[Security Solution] Swap rule unions out for discriminated unions to improve validation error messages (#171452)

**Epics:** https://github.com/elastic/security-team/issues/8058,
https://github.com/elastic/security-team/issues/6726 (internal)
**Partially addresses:**
https://github.com/elastic/security-team/issues/7991 (internal)

## Summary

The main benefit of this PR is shown in `rule_request_schema.test.ts`,
where the error messages are now more accurate and concise. With regular
unions, `zod` has to try validating the input against all schemas in the
union and reports the errors from every schema in the union. Switching
to discriminated unions, with `type` as the discriminator, allows `zod`
to pick the right rule type schema from the union and only validate
against that rule type. This means the error message reports that either
the discriminator is invalid, in any case where `type` is not valid, or
if `type` is valid but another field is wrong for that type of rule then
the error message is the validation result from only that rule type.

To make it possible to use discriminated unions, we need to switch from
using zod's `.and()` for intersections to `.merge()` because `.and()`
returns an intersection type that is incompatible with discriminated
unions in zod. Similarly, we need to remove the usage of `.transform()`
because it returns a ZodEffect that is incompatible with `.merge()`.

Instead of using `.transform()` to turn properties from optional to
possibly undefined, we can use `requiredOptional` explicitly in specific
places to convert the types. Similarly, the `RequiredOptional` type can
be used on the return type of conversion functions between API and
internal schemas to enforce that all properties are explicitly specified
in the conversion.

Future work:
- better alignment of codegen with OpenAPI definitions of anyOf/oneOf.
https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/#oneof
oneOf requires that the input match exactly one schema from the list,
which is different from z.union. anyOf should be z.union, oneOf should
be z.discriminatedUnion
- flatten the schema structure further to avoid `Type instantiation is
excessively deep and possibly infinite`. Seems to be a common issue with
zod (https://github.com/microsoft/TypeScript/issues/34933) Limiting the
number of `.merge` and other zod operations needed to build a particular
schema object seems to help resolve the error. Combining
`ResponseRequiredFields` and `ResponseOptionalFields` into a single
object rather than merging them solved the immediate problem. However,
we may still be near the depth limit. Changing `RuleResponse` as seen
below also solved the problem in testing, and may give us more headroom
for future changes if we apply techniques like this here and in other
places. The difference here is that `SharedResponseProps` is only
intersected with the type specific schemas after they're combined in a
discriminated union, whereas in `main` we merge `SharedResponseProps`
with each individual schema then merge them all together.
- combine other Required and Optional schemas, like
QueryRuleRequiredFields and QueryRuleOptionalFields

```ts
export type RuleResponse = z.infer<typeof RuleResponse>;
export const RuleResponse = SharedResponseProps.and(z.discriminatedUnion('type', [
  EqlRuleResponseFields,
  QueryRuleResponseFields,
  SavedQueryRuleResponseFields,
  ThresholdRuleResponseFields,
  ThreatMatchRuleResponseFields,
  MachineLearningRuleResponseFields,
  NewTermsRuleResponseFields,
  EsqlRuleResponseFields,
]));
```

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Marshall Main 2023-11-29 12:11:29 -08:00 committed by GitHub
parent 12a8e8b918
commit 6073eb6dcd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 272 additions and 333 deletions

View file

@ -12,7 +12,6 @@
{{~#if (defined default)}}.default({{{toJSON default}}}){{/if~}}
{{~#if (eq x-modify "partial")}}.partial(){{/if~}}
{{~#if (eq x-modify "required")}}.required(){{/if~}}
{{~#if (eq x-modify "requiredOptional")}}.transform(requiredOptional){{/if~}}
{{~/if~}}
{{~#if allOf~}}
@ -20,26 +19,34 @@
{{~#if @first~}}
{{> zod_schema_item }}
{{~else~}}
.and({{> zod_schema_item }})
.merge({{> zod_schema_item }})
{{~/if~}}
{{~/each~}}
{{~/if~}}
{{~#if anyOf~}}
z.union([
{{~#each anyOf~}}
{{#if discriminator}}
z.discriminatedUnion('{{discriminator.propertyName}}', [
{{else}}
z.union([
{{/if}}
{{~#each anyOf~}}
{{~> zod_schema_item ~}},
{{~/each~}}
{{~/each~}}
])
{{~#if nullable}}.nullable(){{/if~}}
{{~#if (eq requiredBool false)}}.optional(){{/if~}}
{{~/if~}}
{{~#if oneOf~}}
z.union([
{{~#each oneOf~}}
{{#if discriminator}}
z.discriminatedUnion('{{discriminator.propertyName}}', [
{{else}}
z.union([
{{/if}}
{{~#each oneOf~}}
{{~> zod_schema_item ~}},
{{~/each~}}
{{~/each~}}
])
{{~#if nullable}}.nullable(){{/if~}}
{{~#if (eq requiredBool false)}}.optional(){{/if~}}
@ -97,7 +104,6 @@ z.unknown()
{{~/if~}}
{{~#if (eq x-modify "partial")}}.partial(){{/if~}}
{{~#if (eq x-modify "required")}}.required(){{/if~}}
{{~#if (eq x-modify "requiredOptional")}}.transform(requiredOptional){{/if~}}
{{~/inline~}}
{{~#*inline "type_string"~}}

View file

@ -6,7 +6,6 @@
*/
import { z } from 'zod';
import { requiredOptional } from '@kbn/zod-helpers';
/*
* NOTICE: Do not edit this file manually.
@ -27,48 +26,42 @@ export const EcsMapping = z.object({}).catchall(
);
export type OsqueryQuery = z.infer<typeof OsqueryQuery>;
export const OsqueryQuery = z
.object({
/**
* Query ID
*/
id: z.string(),
/**
* Query to execute
*/
query: z.string(),
ecs_mapping: EcsMapping.optional(),
/**
* Query version
*/
version: z.string().optional(),
platform: z.string().optional(),
removed: z.boolean().optional(),
snapshot: z.boolean().optional(),
})
.transform(requiredOptional);
export const OsqueryQuery = z.object({
/**
* Query ID
*/
id: z.string(),
/**
* Query to execute
*/
query: z.string(),
ecs_mapping: EcsMapping.optional(),
/**
* Query version
*/
version: z.string().optional(),
platform: z.string().optional(),
removed: z.boolean().optional(),
snapshot: z.boolean().optional(),
});
export type OsqueryParams = z.infer<typeof OsqueryParams>;
export const OsqueryParams = z
.object({
query: z.string().optional(),
ecs_mapping: EcsMapping.optional(),
queries: z.array(OsqueryQuery).optional(),
pack_id: z.string().optional(),
saved_query_id: z.string().optional(),
})
.transform(requiredOptional);
export const OsqueryParams = z.object({
query: z.string().optional(),
ecs_mapping: EcsMapping.optional(),
queries: z.array(OsqueryQuery).optional(),
pack_id: z.string().optional(),
saved_query_id: z.string().optional(),
});
export type OsqueryParamsCamelCase = z.infer<typeof OsqueryParamsCamelCase>;
export const OsqueryParamsCamelCase = z
.object({
query: z.string().optional(),
ecsMapping: EcsMapping.optional(),
queries: z.array(OsqueryQuery).optional(),
packId: z.string().optional(),
savedQueryId: z.string().optional(),
})
.transform(requiredOptional);
export const OsqueryParamsCamelCase = z.object({
query: z.string().optional(),
ecsMapping: EcsMapping.optional(),
queries: z.array(OsqueryQuery).optional(),
packId: z.string().optional(),
savedQueryId: z.string().optional(),
});
export type OsqueryResponseAction = z.infer<typeof OsqueryResponseAction>;
export const OsqueryResponseAction = z.object({
@ -83,12 +76,10 @@ export const RuleResponseOsqueryAction = z.object({
});
export type EndpointParams = z.infer<typeof EndpointParams>;
export const EndpointParams = z
.object({
command: z.literal('isolate'),
comment: z.string().optional(),
})
.transform(requiredOptional);
export const EndpointParams = z.object({
command: z.literal('isolate'),
comment: z.string().optional(),
});
export type EndpointResponseAction = z.infer<typeof EndpointResponseAction>;
export const EndpointResponseAction = z.object({

View file

@ -49,7 +49,6 @@ components:
required:
- id
- query
x-modify: requiredOptional
OsqueryParams:
type: object
@ -66,7 +65,6 @@ components:
type: string
saved_query_id:
type: string
x-modify: requiredOptional
OsqueryParamsCamelCase:
type: object
@ -83,7 +81,6 @@ components:
type: string
savedQueryId:
type: string
x-modify: requiredOptional
OsqueryResponseAction:
type: object
@ -123,7 +120,6 @@ components:
type: string
required:
- command
x-modify: requiredOptional
EndpointResponseAction:
type: object

View file

@ -6,7 +6,7 @@
*/
import { z } from 'zod';
import { requiredOptional, isValidDateMath } from '@kbn/zod-helpers';
import { isValidDateMath } from '@kbn/zod-helpers';
/*
* NOTICE: Do not edit this file manually.
@ -94,14 +94,12 @@ export const RiskScore = z.number().int().min(0).max(100);
*/
export type RiskScoreMapping = z.infer<typeof RiskScoreMapping>;
export const RiskScoreMapping = z.array(
z
.object({
field: z.string(),
operator: z.literal('equals'),
value: z.string(),
risk_score: RiskScore.optional(),
})
.transform(requiredOptional)
z.object({
field: z.string(),
operator: z.literal('equals'),
value: z.string(),
risk_score: RiskScore.optional(),
})
);
/**

View file

@ -95,7 +95,6 @@ components:
- field
- operator
- value
x-modify: requiredOptional
description: Overrides generated alerts' risk_score with a value from the source event
Severity:

View file

@ -25,8 +25,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"name: Required, description: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", and 52 more"`
expect(stringifyZodError(result.error)).toEqual(
"type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql'"
);
});
@ -48,8 +48,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"name: Required, description: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", and 52 more"`
expect(stringifyZodError(result.error)).toEqual(
"type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql'"
);
});
@ -61,8 +61,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"name: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, and 44 more"`
expect(stringifyZodError(result.error)).toEqual(
"type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql'"
);
});
@ -75,8 +75,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"name: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, and 44 more"`
expect(stringifyZodError(result.error)).toEqual(
"type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql'"
);
});
@ -90,8 +90,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"name: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, and 44 more"`
expect(stringifyZodError(result.error)).toEqual(
"type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql'"
);
});
@ -106,8 +106,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", and 36 more"`
expect(stringifyZodError(result.error)).toEqual(
"type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql'"
);
});
@ -123,8 +123,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"risk_score: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", risk_score: Required, and 28 more"`
expect(stringifyZodError(result.error)).toEqual(
"type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql'"
);
});
@ -141,9 +141,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"risk_score: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", risk_score: Required, and 27 more"`
);
expect(stringifyZodError(result.error)).toEqual('risk_score: Required');
});
test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => {
@ -160,9 +158,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"risk_score: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", risk_score: Required, and 27 more"`
);
expect(stringifyZodError(result.error)).toEqual('risk_score: Required');
});
test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => {
@ -180,9 +176,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"risk_score: Required, type: Invalid literal value, expected \\"eql\\", query: Required, language: Invalid literal value, expected \\"eql\\", risk_score: Required, and 27 more"`
);
expect(stringifyZodError(result.error)).toEqual('risk_score: Required');
});
test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => {
@ -222,9 +216,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"risk_score: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", risk_score: Required, risk_score: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toEqual('risk_score: Required');
});
test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => {
@ -390,8 +382,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"references.0: Expected string, received number, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", references.0: Expected string, received number, references.0: Expected string, received number, and 22 more"`
expect(stringifyZodError(result.error)).toEqual(
'references.0: Expected string, received number'
);
});
@ -403,9 +395,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", index.0: Expected string, received number, index.0: Expected string, received number, type: Invalid literal value, expected \\"saved_query\\", and 20 more"`
);
expect(stringifyZodError(result.error)).toEqual('index.0: Expected string, received number');
});
test('saved_query type can have filters with it', () => {
@ -427,9 +417,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", filters: Expected array, received string, filters: Expected array, received string, type: Invalid literal value, expected \\"saved_query\\", and 20 more"`
);
expect(stringifyZodError(result.error)).toEqual('filters: Expected array, received string');
});
test('language validates with kuery', () => {
@ -462,8 +450,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up', type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 19 more"`
expect(stringifyZodError(result.error)).toEqual(
"language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up'"
);
});
@ -523,8 +511,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"tags.0: Expected string, received number, tags.1: Expected string, received number, tags.2: Expected string, received number, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", and 38 more"`
expect(stringifyZodError(result.error)).toEqual(
'tags.0: Expected string, received number, tags.1: Expected string, received number, tags.2: Expected string, received number'
);
});
@ -551,9 +539,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"threat.0.framework: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", threat.0.framework: Required, threat.0.framework: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toEqual('threat.0.framework: Required');
});
test('You cannot send in an array of threat that are missing "tactic"', () => {
@ -575,9 +561,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"threat.0.tactic: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", threat.0.tactic: Required, threat.0.tactic: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toEqual('threat.0.tactic: Required');
});
test('You can send in an array of threat that are missing "technique"', () => {
@ -619,8 +603,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"false_positives.0: Expected string, received number, false_positives.1: Expected string, received number, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", false_positives.0: Expected string, received number, and 30 more"`
expect(stringifyZodError(result.error)).toEqual(
'false_positives.0: Expected string, received number, false_positives.1: Expected string, received number'
);
});
@ -693,9 +677,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"meta: Expected object, received string, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", meta: Expected object, received string, meta: Expected object, received string, and 22 more"`
);
expect(stringifyZodError(result.error)).toEqual('meta: Expected object, received string');
});
test('You can omit the query string when filters are present', () => {
@ -730,8 +712,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk', and 22 more"`
expect(stringifyZodError(result.error)).toEqual(
"severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'junk'"
);
});
@ -743,9 +725,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"actions.0.group: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.group: Required, actions.0.group: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toEqual('actions.0.group: Required');
});
test('You cannot send in an array of actions that are missing "id"', () => {
@ -756,9 +736,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"actions.0.id: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.id: Required, actions.0.id: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toEqual('actions.0.id: Required');
});
test('You cannot send in an array of actions that are missing "action_type_id"', () => {
@ -769,9 +747,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"actions.0.action_type_id: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.action_type_id: Required, actions.0.action_type_id: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toEqual('actions.0.action_type_id: Required');
});
test('You cannot send in an array of actions that are missing "params"', () => {
@ -782,9 +758,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"actions.0.params: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.params: Required, actions.0.params: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toEqual('actions.0.params: Required');
});
test('You cannot send in an array of actions that are including "actionTypeId"', () => {
@ -802,9 +776,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"actions.0.action_type_id: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", actions.0.action_type_id: Required, actions.0.action_type_id: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toEqual('actions.0.action_type_id: Required');
});
describe('note', () => {
@ -840,9 +812,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"note: Expected string, received object, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", note: Expected string, received object, note: Expected string, received object, and 22 more"`
);
expect(stringifyZodError(result.error)).toEqual('note: Expected string, received object');
});
test('empty name is not valid', () => {
@ -926,9 +896,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", saved_id: Required, type: Invalid literal value, expected \\"threshold\\", and 14 more"`
);
expect(stringifyZodError(result.error)).toEqual('saved_id: Required');
});
test('threshold is required when type is threshold and will not validate without it', () => {
@ -936,9 +904,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 14 more"`
);
expect(stringifyZodError(result.error)).toEqual('threshold: Required');
});
test('threshold rules fail validation if threshold is not greater than 0', () => {
@ -1016,8 +982,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"exceptions_list.0.list_id: Required, exceptions_list.0.type: Required, exceptions_list.0.namespace_type: Invalid enum value. Expected 'agnostic' | 'single', received 'not a namespace type', type: Invalid literal value, expected \\"eql\\", query: Required, and 43 more"`
expect(stringifyZodError(result.error)).toEqual(
"exceptions_list.0.list_id: Required, exceptions_list.0.type: Required, exceptions_list.0.namespace_type: Invalid enum value. Expected 'agnostic' | 'single', received 'not a namespace type'"
);
});
@ -1059,8 +1025,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 14 more"`
expect(stringifyZodError(result.error)).toEqual(
'threat_query: Required, threat_mapping: Required, threat_index: Required'
);
});
@ -1130,8 +1096,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", data_view_id: Expected string, received number, data_view_id: Expected string, received number, type: Invalid literal value, expected \\"saved_query\\", and 20 more"`
expect(stringifyZodError(result.error)).toEqual(
'data_view_id: Expected string, received number'
);
});
@ -1195,9 +1161,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"investigation_fields.field_names: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", investigation_fields.field_names: Required, investigation_fields.field_names: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toEqual('investigation_fields.field_names: Required');
});
test('You can send in investigation_fields', () => {
@ -1232,8 +1196,8 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"investigation_fields.field_names.0: Expected string, received number, investigation_fields.field_names.1: Expected string, received number, investigation_fields.field_names.2: Expected string, received number, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", and 38 more"`
expect(stringifyZodError(result.error)).toEqual(
'investigation_fields.field_names.0: Expected string, received number, investigation_fields.field_names.1: Expected string, received number, investigation_fields.field_names.2: Expected string, received number'
);
});
@ -1245,9 +1209,7 @@ describe('rules schema', () => {
const result = RuleCreateProps.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"investigation_fields.field_names: Required, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", investigation_fields.field_names: Required, investigation_fields.field_names: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toEqual('investigation_fields.field_names: Required');
});
});
});

View file

@ -41,7 +41,7 @@ describe('Rule response schema', () => {
const result = RuleResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 15 more"`
`"type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql'"`
);
});
@ -70,9 +70,7 @@ describe('Rule response schema', () => {
const result = RuleResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", type: Invalid literal value, expected \\"query\\", saved_id: Required, type: Invalid literal value, expected \\"threshold\\", and 14 more"`
);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"saved_id: Required"`);
});
test('it should validate a type of "timeline_id" if there is a "timeline_title" dependent', () => {
@ -103,7 +101,7 @@ describe('Rule response schema', () => {
const result = RuleResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"exceptions_list: Expected array, received string, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", exceptions_list: Expected array, received string, exceptions_list: Expected array, received string, and 22 more"`
`"exceptions_list: Expected array, received string"`
);
});
});
@ -239,7 +237,7 @@ describe('investigation_fields', () => {
const result = RuleResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"investigation_fields: Expected object, received string, type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", investigation_fields: Expected object, received string, investigation_fields: Expected object, received string, and 22 more"`
`"investigation_fields: Expected object, received string"`
);
});
});

View file

@ -131,20 +131,20 @@ export const BaseDefaultableFields = z.object({
export type BaseCreateProps = z.infer<typeof BaseCreateProps>;
export const BaseCreateProps =
BaseRequiredFields.and(BaseOptionalFields).and(BaseDefaultableFields);
BaseRequiredFields.merge(BaseOptionalFields).merge(BaseDefaultableFields);
export type BasePatchProps = z.infer<typeof BasePatchProps>;
export const BasePatchProps = BaseRequiredFields.partial()
.and(BaseOptionalFields)
.and(BaseDefaultableFields);
.merge(BaseOptionalFields)
.merge(BaseDefaultableFields);
export type BaseResponseProps = z.infer<typeof BaseResponseProps>;
export const BaseResponseProps = BaseRequiredFields.and(BaseOptionalFields).and(
export const BaseResponseProps = BaseRequiredFields.merge(BaseOptionalFields).merge(
BaseDefaultableFields.required()
);
export type ResponseRequiredFields = z.infer<typeof ResponseRequiredFields>;
export const ResponseRequiredFields = z.object({
export type ResponseFields = z.infer<typeof ResponseFields>;
export const ResponseFields = z.object({
id: RuleObjectId,
rule_id: RuleSignatureId,
immutable: IsRuleImmutable,
@ -156,22 +156,18 @@ export const ResponseRequiredFields = z.object({
related_integrations: RelatedIntegrationArray,
required_fields: RequiredFieldArray,
setup: SetupGuide,
});
export type ResponseOptionalFields = z.infer<typeof ResponseOptionalFields>;
export const ResponseOptionalFields = z.object({
execution_summary: RuleExecutionSummary.optional(),
});
export type SharedCreateProps = z.infer<typeof SharedCreateProps>;
export const SharedCreateProps = BaseCreateProps.and(
export const SharedCreateProps = BaseCreateProps.merge(
z.object({
rule_id: RuleSignatureId.optional(),
})
);
export type SharedUpdateProps = z.infer<typeof SharedUpdateProps>;
export const SharedUpdateProps = BaseCreateProps.and(
export const SharedUpdateProps = BaseCreateProps.merge(
z.object({
id: RuleObjectId.optional(),
rule_id: RuleSignatureId.optional(),
@ -179,7 +175,7 @@ export const SharedUpdateProps = BaseCreateProps.and(
);
export type SharedPatchProps = z.infer<typeof SharedPatchProps>;
export const SharedPatchProps = BasePatchProps.and(
export const SharedPatchProps = BasePatchProps.merge(
z.object({
id: RuleObjectId.optional(),
rule_id: RuleSignatureId.optional(),
@ -187,8 +183,7 @@ export const SharedPatchProps = BasePatchProps.and(
);
export type SharedResponseProps = z.infer<typeof SharedResponseProps>;
export const SharedResponseProps =
BaseResponseProps.and(ResponseRequiredFields).and(ResponseOptionalFields);
export const SharedResponseProps = BaseResponseProps.merge(ResponseFields);
export type EqlQueryLanguage = z.infer<typeof EqlQueryLanguage>;
export const EqlQueryLanguage = z.literal('eql');
@ -220,25 +215,25 @@ export const EqlOptionalFields = z.object({
});
export type EqlRuleCreateFields = z.infer<typeof EqlRuleCreateFields>;
export const EqlRuleCreateFields = EqlRequiredFields.and(EqlOptionalFields);
export const EqlRuleCreateFields = EqlRequiredFields.merge(EqlOptionalFields);
export type EqlRuleResponseFields = z.infer<typeof EqlRuleResponseFields>;
export const EqlRuleResponseFields = EqlRequiredFields.and(EqlOptionalFields);
export const EqlRuleResponseFields = EqlRequiredFields.merge(EqlOptionalFields);
export type EqlRulePatchFields = z.infer<typeof EqlRulePatchFields>;
export const EqlRulePatchFields = EqlRequiredFields.partial().and(EqlOptionalFields);
export const EqlRulePatchFields = EqlRequiredFields.partial().merge(EqlOptionalFields);
export type EqlRule = z.infer<typeof EqlRule>;
export const EqlRule = SharedResponseProps.and(EqlRuleResponseFields);
export const EqlRule = SharedResponseProps.merge(EqlRuleResponseFields);
export type EqlRuleCreateProps = z.infer<typeof EqlRuleCreateProps>;
export const EqlRuleCreateProps = SharedCreateProps.and(EqlRuleCreateFields);
export const EqlRuleCreateProps = SharedCreateProps.merge(EqlRuleCreateFields);
export type EqlRuleUpdateProps = z.infer<typeof EqlRuleUpdateProps>;
export const EqlRuleUpdateProps = SharedUpdateProps.and(EqlRuleCreateFields);
export const EqlRuleUpdateProps = SharedUpdateProps.merge(EqlRuleCreateFields);
export type EqlRulePatchProps = z.infer<typeof EqlRulePatchProps>;
export const EqlRulePatchProps = SharedPatchProps.and(EqlRulePatchFields);
export const EqlRulePatchProps = SharedPatchProps.merge(EqlRulePatchFields);
export type QueryRuleRequiredFields = z.infer<typeof QueryRuleRequiredFields>;
export const QueryRuleRequiredFields = z.object({
@ -265,31 +260,31 @@ export const QueryRuleDefaultableFields = z.object({
});
export type QueryRuleCreateFields = z.infer<typeof QueryRuleCreateFields>;
export const QueryRuleCreateFields = QueryRuleRequiredFields.and(QueryRuleOptionalFields).and(
export const QueryRuleCreateFields = QueryRuleRequiredFields.merge(QueryRuleOptionalFields).merge(
QueryRuleDefaultableFields
);
export type QueryRulePatchFields = z.infer<typeof QueryRulePatchFields>;
export const QueryRulePatchFields = QueryRuleRequiredFields.partial()
.and(QueryRuleOptionalFields)
.and(QueryRuleDefaultableFields);
.merge(QueryRuleOptionalFields)
.merge(QueryRuleDefaultableFields);
export type QueryRuleResponseFields = z.infer<typeof QueryRuleResponseFields>;
export const QueryRuleResponseFields = QueryRuleRequiredFields.and(QueryRuleOptionalFields).and(
export const QueryRuleResponseFields = QueryRuleRequiredFields.merge(QueryRuleOptionalFields).merge(
QueryRuleDefaultableFields.required()
);
export type QueryRule = z.infer<typeof QueryRule>;
export const QueryRule = SharedResponseProps.and(QueryRuleResponseFields);
export const QueryRule = SharedResponseProps.merge(QueryRuleResponseFields);
export type QueryRuleCreateProps = z.infer<typeof QueryRuleCreateProps>;
export const QueryRuleCreateProps = SharedCreateProps.and(QueryRuleCreateFields);
export const QueryRuleCreateProps = SharedCreateProps.merge(QueryRuleCreateFields);
export type QueryRuleUpdateProps = z.infer<typeof QueryRuleUpdateProps>;
export const QueryRuleUpdateProps = SharedUpdateProps.and(QueryRuleCreateFields);
export const QueryRuleUpdateProps = SharedUpdateProps.merge(QueryRuleCreateFields);
export type QueryRulePatchProps = z.infer<typeof QueryRulePatchProps>;
export const QueryRulePatchProps = SharedPatchProps.and(QueryRulePatchFields);
export const QueryRulePatchProps = SharedPatchProps.merge(QueryRulePatchFields);
export type SavedQueryRuleRequiredFields = z.infer<typeof SavedQueryRuleRequiredFields>;
export const SavedQueryRuleRequiredFields = z.object({
@ -316,31 +311,31 @@ export const SavedQueryRuleDefaultableFields = z.object({
});
export type SavedQueryRuleCreateFields = z.infer<typeof SavedQueryRuleCreateFields>;
export const SavedQueryRuleCreateFields = SavedQueryRuleRequiredFields.and(
export const SavedQueryRuleCreateFields = SavedQueryRuleRequiredFields.merge(
SavedQueryRuleOptionalFields
).and(SavedQueryRuleDefaultableFields);
).merge(SavedQueryRuleDefaultableFields);
export type SavedQueryRulePatchFields = z.infer<typeof SavedQueryRulePatchFields>;
export const SavedQueryRulePatchFields = SavedQueryRuleRequiredFields.partial()
.and(SavedQueryRuleOptionalFields)
.and(SavedQueryRuleDefaultableFields);
.merge(SavedQueryRuleOptionalFields)
.merge(SavedQueryRuleDefaultableFields);
export type SavedQueryRuleResponseFields = z.infer<typeof SavedQueryRuleResponseFields>;
export const SavedQueryRuleResponseFields = SavedQueryRuleRequiredFields.and(
export const SavedQueryRuleResponseFields = SavedQueryRuleRequiredFields.merge(
SavedQueryRuleOptionalFields
).and(SavedQueryRuleDefaultableFields.required());
).merge(SavedQueryRuleDefaultableFields.required());
export type SavedQueryRule = z.infer<typeof SavedQueryRule>;
export const SavedQueryRule = SharedResponseProps.and(SavedQueryRuleResponseFields);
export const SavedQueryRule = SharedResponseProps.merge(SavedQueryRuleResponseFields);
export type SavedQueryRuleCreateProps = z.infer<typeof SavedQueryRuleCreateProps>;
export const SavedQueryRuleCreateProps = SharedCreateProps.and(SavedQueryRuleCreateFields);
export const SavedQueryRuleCreateProps = SharedCreateProps.merge(SavedQueryRuleCreateFields);
export type SavedQueryRuleUpdateProps = z.infer<typeof SavedQueryRuleUpdateProps>;
export const SavedQueryRuleUpdateProps = SharedUpdateProps.and(SavedQueryRuleCreateFields);
export const SavedQueryRuleUpdateProps = SharedUpdateProps.merge(SavedQueryRuleCreateFields);
export type SavedQueryRulePatchProps = z.infer<typeof SavedQueryRulePatchProps>;
export const SavedQueryRulePatchProps = SharedPatchProps.and(SavedQueryRulePatchFields);
export const SavedQueryRulePatchProps = SharedPatchProps.merge(SavedQueryRulePatchFields);
export type ThresholdRuleRequiredFields = z.infer<typeof ThresholdRuleRequiredFields>;
export const ThresholdRuleRequiredFields = z.object({
@ -366,31 +361,31 @@ export const ThresholdRuleDefaultableFields = z.object({
});
export type ThresholdRuleCreateFields = z.infer<typeof ThresholdRuleCreateFields>;
export const ThresholdRuleCreateFields = ThresholdRuleRequiredFields.and(
export const ThresholdRuleCreateFields = ThresholdRuleRequiredFields.merge(
ThresholdRuleOptionalFields
).and(ThresholdRuleDefaultableFields);
).merge(ThresholdRuleDefaultableFields);
export type ThresholdRulePatchFields = z.infer<typeof ThresholdRulePatchFields>;
export const ThresholdRulePatchFields = ThresholdRuleRequiredFields.partial()
.and(ThresholdRuleOptionalFields)
.and(ThresholdRuleDefaultableFields);
.merge(ThresholdRuleOptionalFields)
.merge(ThresholdRuleDefaultableFields);
export type ThresholdRuleResponseFields = z.infer<typeof ThresholdRuleResponseFields>;
export const ThresholdRuleResponseFields = ThresholdRuleRequiredFields.and(
export const ThresholdRuleResponseFields = ThresholdRuleRequiredFields.merge(
ThresholdRuleOptionalFields
).and(ThresholdRuleDefaultableFields.required());
).merge(ThresholdRuleDefaultableFields.required());
export type ThresholdRule = z.infer<typeof ThresholdRule>;
export const ThresholdRule = SharedResponseProps.and(ThresholdRuleResponseFields);
export const ThresholdRule = SharedResponseProps.merge(ThresholdRuleResponseFields);
export type ThresholdRuleCreateProps = z.infer<typeof ThresholdRuleCreateProps>;
export const ThresholdRuleCreateProps = SharedCreateProps.and(ThresholdRuleCreateFields);
export const ThresholdRuleCreateProps = SharedCreateProps.merge(ThresholdRuleCreateFields);
export type ThresholdRuleUpdateProps = z.infer<typeof ThresholdRuleUpdateProps>;
export const ThresholdRuleUpdateProps = SharedUpdateProps.and(ThresholdRuleCreateFields);
export const ThresholdRuleUpdateProps = SharedUpdateProps.merge(ThresholdRuleCreateFields);
export type ThresholdRulePatchProps = z.infer<typeof ThresholdRulePatchProps>;
export const ThresholdRulePatchProps = SharedPatchProps.and(ThresholdRulePatchFields);
export const ThresholdRulePatchProps = SharedPatchProps.merge(ThresholdRulePatchFields);
export type ThreatMatchRuleRequiredFields = z.infer<typeof ThreatMatchRuleRequiredFields>;
export const ThreatMatchRuleRequiredFields = z.object({
@ -423,31 +418,31 @@ export const ThreatMatchRuleDefaultableFields = z.object({
});
export type ThreatMatchRuleCreateFields = z.infer<typeof ThreatMatchRuleCreateFields>;
export const ThreatMatchRuleCreateFields = ThreatMatchRuleRequiredFields.and(
export const ThreatMatchRuleCreateFields = ThreatMatchRuleRequiredFields.merge(
ThreatMatchRuleOptionalFields
).and(ThreatMatchRuleDefaultableFields);
).merge(ThreatMatchRuleDefaultableFields);
export type ThreatMatchRulePatchFields = z.infer<typeof ThreatMatchRulePatchFields>;
export const ThreatMatchRulePatchFields = ThreatMatchRuleRequiredFields.partial()
.and(ThreatMatchRuleOptionalFields)
.and(ThreatMatchRuleDefaultableFields);
.merge(ThreatMatchRuleOptionalFields)
.merge(ThreatMatchRuleDefaultableFields);
export type ThreatMatchRuleResponseFields = z.infer<typeof ThreatMatchRuleResponseFields>;
export const ThreatMatchRuleResponseFields = ThreatMatchRuleRequiredFields.and(
export const ThreatMatchRuleResponseFields = ThreatMatchRuleRequiredFields.merge(
ThreatMatchRuleOptionalFields
).and(ThreatMatchRuleDefaultableFields.required());
).merge(ThreatMatchRuleDefaultableFields.required());
export type ThreatMatchRule = z.infer<typeof ThreatMatchRule>;
export const ThreatMatchRule = SharedResponseProps.and(ThreatMatchRuleResponseFields);
export const ThreatMatchRule = SharedResponseProps.merge(ThreatMatchRuleResponseFields);
export type ThreatMatchRuleCreateProps = z.infer<typeof ThreatMatchRuleCreateProps>;
export const ThreatMatchRuleCreateProps = SharedCreateProps.and(ThreatMatchRuleCreateFields);
export const ThreatMatchRuleCreateProps = SharedCreateProps.merge(ThreatMatchRuleCreateFields);
export type ThreatMatchRuleUpdateProps = z.infer<typeof ThreatMatchRuleUpdateProps>;
export const ThreatMatchRuleUpdateProps = SharedUpdateProps.and(ThreatMatchRuleCreateFields);
export const ThreatMatchRuleUpdateProps = SharedUpdateProps.merge(ThreatMatchRuleCreateFields);
export type ThreatMatchRulePatchProps = z.infer<typeof ThreatMatchRulePatchProps>;
export const ThreatMatchRulePatchProps = SharedPatchProps.and(ThreatMatchRulePatchFields);
export const ThreatMatchRulePatchProps = SharedPatchProps.merge(ThreatMatchRulePatchFields);
export type MachineLearningRuleRequiredFields = z.infer<typeof MachineLearningRuleRequiredFields>;
export const MachineLearningRuleRequiredFields = z.object({
@ -469,20 +464,20 @@ export type MachineLearningRuleCreateFields = z.infer<typeof MachineLearningRule
export const MachineLearningRuleCreateFields = MachineLearningRuleRequiredFields;
export type MachineLearningRule = z.infer<typeof MachineLearningRule>;
export const MachineLearningRule = SharedResponseProps.and(MachineLearningRuleResponseFields);
export const MachineLearningRule = SharedResponseProps.merge(MachineLearningRuleResponseFields);
export type MachineLearningRuleCreateProps = z.infer<typeof MachineLearningRuleCreateProps>;
export const MachineLearningRuleCreateProps = SharedCreateProps.and(
export const MachineLearningRuleCreateProps = SharedCreateProps.merge(
MachineLearningRuleCreateFields
);
export type MachineLearningRuleUpdateProps = z.infer<typeof MachineLearningRuleUpdateProps>;
export const MachineLearningRuleUpdateProps = SharedUpdateProps.and(
export const MachineLearningRuleUpdateProps = SharedUpdateProps.merge(
MachineLearningRuleCreateFields
);
export type MachineLearningRulePatchProps = z.infer<typeof MachineLearningRulePatchProps>;
export const MachineLearningRulePatchProps = SharedPatchProps.and(MachineLearningRulePatchFields);
export const MachineLearningRulePatchProps = SharedPatchProps.merge(MachineLearningRulePatchFields);
export type NewTermsRuleRequiredFields = z.infer<typeof NewTermsRuleRequiredFields>;
export const NewTermsRuleRequiredFields = z.object({
@ -509,30 +504,30 @@ export const NewTermsRuleDefaultableFields = z.object({
export type NewTermsRulePatchFields = z.infer<typeof NewTermsRulePatchFields>;
export const NewTermsRulePatchFields = NewTermsRuleRequiredFields.partial()
.and(NewTermsRuleOptionalFields)
.and(NewTermsRuleDefaultableFields);
.merge(NewTermsRuleOptionalFields)
.merge(NewTermsRuleDefaultableFields);
export type NewTermsRuleResponseFields = z.infer<typeof NewTermsRuleResponseFields>;
export const NewTermsRuleResponseFields = NewTermsRuleRequiredFields.and(
export const NewTermsRuleResponseFields = NewTermsRuleRequiredFields.merge(
NewTermsRuleOptionalFields
).and(NewTermsRuleDefaultableFields.required());
).merge(NewTermsRuleDefaultableFields.required());
export type NewTermsRuleCreateFields = z.infer<typeof NewTermsRuleCreateFields>;
export const NewTermsRuleCreateFields = NewTermsRuleRequiredFields.and(
export const NewTermsRuleCreateFields = NewTermsRuleRequiredFields.merge(
NewTermsRuleOptionalFields
).and(NewTermsRuleDefaultableFields);
).merge(NewTermsRuleDefaultableFields);
export type NewTermsRule = z.infer<typeof NewTermsRule>;
export const NewTermsRule = SharedResponseProps.and(NewTermsRuleResponseFields);
export const NewTermsRule = SharedResponseProps.merge(NewTermsRuleResponseFields);
export type NewTermsRuleCreateProps = z.infer<typeof NewTermsRuleCreateProps>;
export const NewTermsRuleCreateProps = SharedCreateProps.and(NewTermsRuleCreateFields);
export const NewTermsRuleCreateProps = SharedCreateProps.merge(NewTermsRuleCreateFields);
export type NewTermsRuleUpdateProps = z.infer<typeof NewTermsRuleUpdateProps>;
export const NewTermsRuleUpdateProps = SharedUpdateProps.and(NewTermsRuleCreateFields);
export const NewTermsRuleUpdateProps = SharedUpdateProps.merge(NewTermsRuleCreateFields);
export type NewTermsRulePatchProps = z.infer<typeof NewTermsRulePatchProps>;
export const NewTermsRulePatchProps = SharedPatchProps.and(NewTermsRulePatchFields);
export const NewTermsRulePatchProps = SharedPatchProps.merge(NewTermsRulePatchFields);
export type EsqlQueryLanguage = z.infer<typeof EsqlQueryLanguage>;
export const EsqlQueryLanguage = z.literal('esql');
@ -560,19 +555,19 @@ export type EsqlRuleCreateFields = z.infer<typeof EsqlRuleCreateFields>;
export const EsqlRuleCreateFields = EsqlRuleRequiredFields;
export type EsqlRule = z.infer<typeof EsqlRule>;
export const EsqlRule = SharedResponseProps.and(EsqlRuleResponseFields);
export const EsqlRule = SharedResponseProps.merge(EsqlRuleResponseFields);
export type EsqlRuleCreateProps = z.infer<typeof EsqlRuleCreateProps>;
export const EsqlRuleCreateProps = SharedCreateProps.and(EsqlRuleCreateFields);
export const EsqlRuleCreateProps = SharedCreateProps.merge(EsqlRuleCreateFields);
export type EsqlRuleUpdateProps = z.infer<typeof EsqlRuleUpdateProps>;
export const EsqlRuleUpdateProps = SharedUpdateProps.and(EsqlRuleCreateFields);
export const EsqlRuleUpdateProps = SharedUpdateProps.merge(EsqlRuleCreateFields);
export type EsqlRulePatchProps = z.infer<typeof EsqlRulePatchProps>;
export const EsqlRulePatchProps = SharedPatchProps.and(EsqlRulePatchFields.partial());
export const EsqlRulePatchProps = SharedPatchProps.merge(EsqlRulePatchFields.partial());
export type TypeSpecificCreateProps = z.infer<typeof TypeSpecificCreateProps>;
export const TypeSpecificCreateProps = z.union([
export const TypeSpecificCreateProps = z.discriminatedUnion('type', [
EqlRuleCreateFields,
QueryRuleCreateFields,
SavedQueryRuleCreateFields,
@ -596,7 +591,7 @@ export const TypeSpecificPatchProps = z.union([
]);
export type TypeSpecificResponse = z.infer<typeof TypeSpecificResponse>;
export const TypeSpecificResponse = z.union([
export const TypeSpecificResponse = z.discriminatedUnion('type', [
EqlRuleResponseFields,
QueryRuleResponseFields,
SavedQueryRuleResponseFields,
@ -608,7 +603,7 @@ export const TypeSpecificResponse = z.union([
]);
export type RuleCreateProps = z.infer<typeof RuleCreateProps>;
export const RuleCreateProps = z.union([
export const RuleCreateProps = z.discriminatedUnion('type', [
EqlRuleCreateProps,
QueryRuleCreateProps,
SavedQueryRuleCreateProps,
@ -620,7 +615,7 @@ export const RuleCreateProps = z.union([
]);
export type RuleUpdateProps = z.infer<typeof RuleUpdateProps>;
export const RuleUpdateProps = z.union([
export const RuleUpdateProps = z.discriminatedUnion('type', [
EqlRuleUpdateProps,
QueryRuleUpdateProps,
SavedQueryRuleUpdateProps,
@ -644,7 +639,7 @@ export const RulePatchProps = z.union([
]);
export type RuleResponse = z.infer<typeof RuleResponse>;
export const RuleResponse = z.union([
export const RuleResponse = z.discriminatedUnion('type', [
EqlRule,
QueryRule,
SavedQueryRule,

View file

@ -152,7 +152,7 @@ components:
- $ref: '#/components/schemas/BaseDefaultableFields'
x-modify: required
ResponseRequiredFields:
ResponseFields:
type: object
properties:
id:
@ -185,6 +185,8 @@ components:
$ref: './common_attributes.schema.yaml#/components/schemas/RequiredFieldArray'
setup:
$ref: './common_attributes.schema.yaml#/components/schemas/SetupGuide'
execution_summary:
$ref: '../../rule_monitoring/model/execution_summary.schema.yaml#/components/schemas/RuleExecutionSummary'
required:
- id
- rule_id
@ -198,12 +200,6 @@ components:
- required_fields
- setup
ResponseOptionalFields:
type: object
properties:
execution_summary:
$ref: '../../rule_monitoring/model/execution_summary.schema.yaml#/components/schemas/RuleExecutionSummary'
SharedCreateProps:
x-inline: true
allOf:
@ -239,8 +235,7 @@ components:
x-inline: true
allOf:
- $ref: '#/components/schemas/BaseResponseProps'
- $ref: '#/components/schemas/ResponseRequiredFields'
- $ref: '#/components/schemas/ResponseOptionalFields'
- $ref: '#/components/schemas/ResponseFields'
############
# EQL Rule #
@ -851,6 +846,8 @@ components:
##########################
TypeSpecificCreateProps:
discriminator:
propertyName: type
anyOf:
- $ref: '#/components/schemas/EqlRuleCreateFields'
- $ref: '#/components/schemas/QueryRuleCreateFields'
@ -873,6 +870,8 @@ components:
- $ref: '#/components/schemas/EsqlRulePatchFields'
TypeSpecificResponse:
discriminator:
propertyName: type
anyOf:
- $ref: '#/components/schemas/EqlRuleResponseFields'
- $ref: '#/components/schemas/QueryRuleResponseFields'
@ -884,6 +883,8 @@ components:
- $ref: '#/components/schemas/EsqlRuleResponseFields'
RuleCreateProps:
discriminator:
propertyName: type
anyOf:
- $ref: '#/components/schemas/EqlRuleCreateProps'
- $ref: '#/components/schemas/QueryRuleCreateProps'
@ -895,6 +896,8 @@ components:
- $ref: '#/components/schemas/EsqlRuleCreateProps'
RuleUpdateProps:
discriminator:
propertyName: type
anyOf:
- $ref: '#/components/schemas/EqlRuleUpdateProps'
- $ref: '#/components/schemas/QueryRuleUpdateProps'
@ -917,6 +920,8 @@ components:
- $ref: '#/components/schemas/EsqlRulePatchProps'
RuleResponse:
discriminator:
propertyName: type
anyOf:
- $ref: '#/components/schemas/EqlRule'
- $ref: '#/components/schemas/QueryRule'

View file

@ -105,35 +105,35 @@ export const BulkActionBase = z.object({
});
export type BulkDeleteRules = z.infer<typeof BulkDeleteRules>;
export const BulkDeleteRules = BulkActionBase.and(
export const BulkDeleteRules = BulkActionBase.merge(
z.object({
action: z.literal('delete'),
})
);
export type BulkDisableRules = z.infer<typeof BulkDisableRules>;
export const BulkDisableRules = BulkActionBase.and(
export const BulkDisableRules = BulkActionBase.merge(
z.object({
action: z.literal('disable'),
})
);
export type BulkEnableRules = z.infer<typeof BulkEnableRules>;
export const BulkEnableRules = BulkActionBase.and(
export const BulkEnableRules = BulkActionBase.merge(
z.object({
action: z.literal('enable'),
})
);
export type BulkExportRules = z.infer<typeof BulkExportRules>;
export const BulkExportRules = BulkActionBase.and(
export const BulkExportRules = BulkActionBase.merge(
z.object({
action: z.literal('export'),
})
);
export type BulkDuplicateRules = z.infer<typeof BulkDuplicateRules>;
export const BulkDuplicateRules = BulkActionBase.and(
export const BulkDuplicateRules = BulkActionBase.merge(
z.object({
action: z.literal('duplicate'),
duplicate: z
@ -254,7 +254,7 @@ export const BulkActionEditPayload = z.union([
]);
export type BulkEditRules = z.infer<typeof BulkEditRules>;
export const BulkEditRules = BulkActionBase.and(
export const BulkEditRules = BulkActionBase.merge(
z.object({
action: z.literal('edit'),
/**

View file

@ -27,7 +27,7 @@ describe('Bulk create rules request schema', () => {
const result = BulkCreateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.name: Required, 0.description: Required, 0.risk_score: Required, 0.severity: Required, 0.type: Invalid literal value, expected \\"eql\\", and 52 more"`
`"0.type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql'"`
);
});
@ -58,9 +58,7 @@ describe('Bulk create rules request schema', () => {
const result = BulkCreateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0.risk_score: Required"`);
});
test('two array elements where the first is valid but the second is invalid (risk_score) will not validate', () => {
@ -72,9 +70,7 @@ describe('Bulk create rules request schema', () => {
const result = BulkCreateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"1.risk_score: Required, 1.type: Invalid literal value, expected \\"eql\\", 1.language: Invalid literal value, expected \\"eql\\", 1.risk_score: Required, 1.risk_score: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"1.risk_score: Required"`);
});
test('two array elements where the first is invalid (risk_score) but the second is valid will not validate', () => {
@ -86,9 +82,7 @@ describe('Bulk create rules request schema', () => {
const result = BulkCreateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0.risk_score: Required"`);
});
test('two array elements where both are invalid (risk_score) will not validate', () => {
@ -103,7 +97,7 @@ describe('Bulk create rules request schema', () => {
const result = BulkCreateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 49 more"`
`"0.risk_score: Required, 1.risk_score: Required"`
);
});
@ -130,7 +124,7 @@ describe('Bulk create rules request schema', () => {
const result = BulkCreateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', 0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', and 22 more"`
`"0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup'"`
);
});
@ -165,7 +159,7 @@ describe('Bulk create rules request schema', () => {
const result = BulkCreateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.note: Expected string, received object, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.note: Expected string, received object, 0.note: Expected string, received object, and 22 more"`
`"0.note: Expected string, received object"`
);
});
});

View file

@ -28,7 +28,7 @@ describe('Bulk update rules request schema', () => {
const result = BulkUpdateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.name: Required, 0.description: Required, 0.risk_score: Required, 0.severity: Required, 0.type: Invalid literal value, expected \\"eql\\", and 52 more"`
`"0.type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql'"`
);
});
@ -59,9 +59,7 @@ describe('Bulk update rules request schema', () => {
const result = BulkUpdateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0.risk_score: Required"`);
});
test('two array elements where the first is valid but the second is invalid (risk_score) will not validate', () => {
@ -73,9 +71,7 @@ describe('Bulk update rules request schema', () => {
const result = BulkUpdateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"1.risk_score: Required, 1.type: Invalid literal value, expected \\"eql\\", 1.language: Invalid literal value, expected \\"eql\\", 1.risk_score: Required, 1.risk_score: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"1.risk_score: Required"`);
});
test('two array elements where the first is invalid (risk_score) but the second is valid will not validate', () => {
@ -87,9 +83,7 @@ describe('Bulk update rules request schema', () => {
const result = BulkUpdateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 22 more"`
);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(`"0.risk_score: Required"`);
});
test('two array elements where both are invalid (risk_score) will not validate', () => {
@ -104,7 +98,7 @@ describe('Bulk update rules request schema', () => {
const result = BulkUpdateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.risk_score: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.risk_score: Required, 0.risk_score: Required, and 49 more"`
`"0.risk_score: Required, 1.risk_score: Required"`
);
});
@ -131,7 +125,7 @@ describe('Bulk update rules request schema', () => {
const result = BulkUpdateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', 0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup', and 22 more"`
`"0.severity: Invalid enum value. Expected 'low' | 'medium' | 'high' | 'critical', received 'madeup'"`
);
});
@ -176,7 +170,7 @@ describe('Bulk update rules request schema', () => {
const result = BulkUpdateRulesRequestBody.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.note: Expected string, received object, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", 0.note: Expected string, received object, 0.note: Expected string, received object, and 22 more"`
`"0.note: Expected string, received object"`
);
});
});

View file

@ -46,7 +46,7 @@ describe('Bulk CRUD rules response schema', () => {
const result = BulkCrudRulesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.error: Required, 0: Unrecognized key(s) in object: 'author', 'created_at', 'updated_at', 'created_by', 'description', 'enabled', 'false_positives', 'from', 'immutable', 'references', 'revision', 'severity', 'severity_mapping', 'updated_by', 'tags', 'to', 'threat', 'version', 'output_index', 'max_signals', 'risk_score', 'risk_score_mapping', 'interval', 'exceptions_list', 'related_integrations', 'required_fields', 'setup', 'throttle', 'actions', 'building_block_type', 'note', 'license', 'outcome', 'alias_target_id', 'alias_purpose', 'timeline_id', 'timeline_title', 'meta', 'rule_name_override', 'timestamp_override', 'timestamp_override_fallback_disabled', 'namespace', 'investigation_fields', 'query', 'type', 'language', 'index', 'data_view_id', 'filters', 'saved_id', 'response_actions', 'alert_suppression', 0.name: Required, 0.type: Invalid literal value, expected \\"eql\\", 0.language: Invalid literal value, expected \\"eql\\", and 24 more"`
`"0.name: Required, 0.error: Required, 0: Unrecognized key(s) in object: 'author', 'created_at', 'updated_at', 'created_by', 'description', 'enabled', 'false_positives', 'from', 'immutable', 'references', 'revision', 'severity', 'severity_mapping', 'updated_by', 'tags', 'to', 'threat', 'version', 'output_index', 'max_signals', 'risk_score', 'risk_score_mapping', 'interval', 'exceptions_list', 'related_integrations', 'required_fields', 'setup', 'throttle', 'actions', 'building_block_type', 'note', 'license', 'outcome', 'alias_target_id', 'alias_purpose', 'timeline_id', 'timeline_title', 'meta', 'rule_name_override', 'timestamp_override', 'timestamp_override_fallback_disabled', 'namespace', 'investigation_fields', 'query', 'type', 'language', 'index', 'data_view_id', 'filters', 'saved_id', 'response_actions', 'alert_suppression'"`
);
});
@ -59,7 +59,7 @@ describe('Bulk CRUD rules response schema', () => {
const result = BulkCrudRulesResponse.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"0.error: Required, 0.name: Required, 0.description: Required, 0.risk_score: Required, 0.severity: Required, and 267 more"`
`"0.type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql', 0.error: Required"`
);
});

View file

@ -22,7 +22,7 @@ describe('RuleToImport', () => {
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"name: Required, description: Required, risk_score: Required, severity: Required, rule_id: Required, and 25 more"`
`"name: Required, description: Required, risk_score: Required, severity: Required, type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql', and 1 more"`
);
});
@ -47,7 +47,7 @@ describe('RuleToImport', () => {
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"name: Required, description: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", and 24 more"`
`"name: Required, description: Required, risk_score: Required, severity: Required, type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql'"`
);
});
@ -61,7 +61,7 @@ describe('RuleToImport', () => {
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"name: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, and 23 more"`
`"name: Required, risk_score: Required, severity: Required, type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql'"`
);
});
@ -76,7 +76,7 @@ describe('RuleToImport', () => {
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"name: Required, risk_score: Required, severity: Required, type: Invalid literal value, expected \\"eql\\", query: Required, and 23 more"`
`"name: Required, risk_score: Required, severity: Required, type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql'"`
);
});
@ -330,7 +330,7 @@ describe('RuleToImport', () => {
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", index.0: Expected string, received number, index.0: Expected string, received number, type: Invalid literal value, expected \\"saved_query\\", and 20 more"`
`"index.0: Expected string, received number"`
);
});
@ -378,7 +378,7 @@ describe('RuleToImport', () => {
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", filters: Expected array, received string, filters: Expected array, received string, type: Invalid literal value, expected \\"saved_query\\", and 20 more"`
`"filters: Expected array, received string"`
);
});
@ -414,7 +414,7 @@ describe('RuleToImport', () => {
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up', type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 19 more"`
`"language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up'"`
);
});

View file

@ -8,7 +8,7 @@
import * as z from 'zod';
import {
BaseCreateProps,
ResponseRequiredFields,
ResponseFields,
RuleSignatureId,
TypeSpecificCreateProps,
} from '../../model/rule_schema';
@ -26,7 +26,7 @@ import {
export type RuleToImport = z.infer<typeof RuleToImport>;
export type RuleToImportInput = z.input<typeof RuleToImport>;
export const RuleToImport = BaseCreateProps.and(TypeSpecificCreateProps).and(
ResponseRequiredFields.partial().extend({
ResponseFields.partial().extend({
rule_id: RuleSignatureId,
immutable: z.literal(false).default(false),
})

View file

@ -15,7 +15,7 @@ import { z } from 'zod';
import { BaseActionSchema, Command, Timeout } from '../model/schema/common.gen';
export type ExecuteActionRequestBody = z.infer<typeof ExecuteActionRequestBody>;
export const ExecuteActionRequestBody = BaseActionSchema.and(
export const ExecuteActionRequestBody = BaseActionSchema.merge(
z.object({
parameters: z.object({
command: Command,

View file

@ -15,7 +15,7 @@ import { z } from 'zod';
import { BaseActionSchema } from '../model/schema/common.gen';
export type FileUploadActionRequestBody = z.infer<typeof FileUploadActionRequestBody>;
export const FileUploadActionRequestBody = BaseActionSchema.and(
export const FileUploadActionRequestBody = BaseActionSchema.merge(
z.object({
parameters: z.object({
overwrite: z.boolean().optional().default(false),

View file

@ -15,7 +15,7 @@ import { z } from 'zod';
import { BaseActionSchema } from '../model/schema/common.gen';
export type GetFileActionRequestBody = z.infer<typeof GetFileActionRequestBody>;
export const GetFileActionRequestBody = BaseActionSchema.and(
export const GetFileActionRequestBody = BaseActionSchema.merge(
z.object({
parameters: z.object({
path: z.string(),

View file

@ -145,7 +145,7 @@ export const BaseActionSchema = z.object({
});
export type ProcessActionSchemas = z.infer<typeof ProcessActionSchemas>;
export const ProcessActionSchemas = BaseActionSchema.and(
export const ProcessActionSchemas = BaseActionSchema.merge(
z.object({
parameters: z.union([
z.object({

View file

@ -25,6 +25,7 @@ import type {
Threats,
} from '@kbn/securitysolution-io-ts-alerting-types';
import { ALERT_RISK_SCORE } from '@kbn/rule-data-utils';
import { requiredOptional } from '@kbn/zod-helpers';
import type { RuleResponse } from '../../../../../common/api/detection_engine/model/rule_schema';
import { SeverityBadge } from '../../../../detections/components/rules/severity_badge';
import { defaultToEmptyTag } from '../../../../common/components/empty_value';
@ -333,7 +334,9 @@ const prepareAboutSectionListItems = (
) : (
''
),
description: <RiskScoreMappingItem riskScoreMappingItem={riskScoreMappingItem} />,
description: (
<RiskScoreMappingItem riskScoreMappingItem={requiredOptional(riskScoreMappingItem)} />
),
};
})
);

View file

@ -21,6 +21,7 @@ import type {
import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants';
import type { Filter } from '@kbn/es-query';
import type { ActionVariables } from '@kbn/triggers-actions-ui-plugin/public';
import { requiredOptional } from '@kbn/zod-helpers';
import type { ResponseAction } from '../../../../../common/api/detection_engine/model/rule_response_actions';
import { normalizeThresholdField } from '../../../../../common/detection_engine/utils';
import { assertUnreachable } from '../../../../../common/utility_types';
@ -253,7 +254,7 @@ export const getAboutStepsData = (rule: RuleResponse, detailsView: boolean): Abo
tags,
riskScore: {
value: riskScore,
mapping: riskScoreMapping,
mapping: requiredOptional(riskScoreMapping),
isMappingChecked: riskScoreMapping.length > 0,
},
falsePositives,

View file

@ -6,6 +6,7 @@
*/
import type { RuleActionArray } from '@kbn/securitysolution-io-ts-alerting-types';
import { requiredOptional } from '@kbn/zod-helpers';
import { DEFAULT_MAX_SIGNALS } from '../../../../../../../common/constants';
import { assertUnreachable } from '../../../../../../../common/utility_types';
import type {
@ -123,7 +124,7 @@ const extractDiffableCommonFields = (
severity: rule.severity,
severity_mapping: rule.severity_mapping ?? [],
risk_score: rule.risk_score,
risk_score_mapping: rule.risk_score_mapping ?? [],
risk_score_mapping: rule.risk_score_mapping?.map((mapping) => requiredOptional(mapping)) ?? [],
// About -> Advanced settings
references: rule.references ?? [],

View file

@ -17,7 +17,7 @@ describe('Prebuilt rule asset schema', () => {
const result = PrebuiltRuleAsset.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"name: Required, description: Required, risk_score: Required, severity: Required, rule_id: Required, and 26 more"`
`"name: Required, description: Required, risk_score: Required, severity: Required, type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql', and 2 more"`
);
});
@ -40,7 +40,7 @@ describe('Prebuilt rule asset schema', () => {
const result = PrebuiltRuleAsset.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"name: Required, description: Required, risk_score: Required, severity: Required, version: Required, and 25 more"`
`"name: Required, description: Required, risk_score: Required, severity: Required, type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql', and 1 more"`
);
});
@ -177,7 +177,7 @@ describe('Prebuilt rule asset schema', () => {
const result = PrebuiltRuleAsset.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", index.0: Expected string, received number, index.0: Expected string, received number, type: Invalid literal value, expected \\"saved_query\\", and 20 more"`
`"index.0: Expected string, received number"`
);
});
@ -201,7 +201,7 @@ describe('Prebuilt rule asset schema', () => {
const result = PrebuiltRuleAsset.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", filters: Expected array, received string, filters: Expected array, received string, type: Invalid literal value, expected \\"saved_query\\", and 20 more"`
`"filters: Expected array, received string"`
);
});
@ -236,7 +236,7 @@ describe('Prebuilt rule asset schema', () => {
const result = PrebuiltRuleAsset.safeParse(payload);
expectParseError(result);
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
`"type: Invalid literal value, expected \\"eql\\", language: Invalid literal value, expected \\"eql\\", language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up', type: Invalid literal value, expected \\"saved_query\\", saved_id: Required, and 19 more"`
`"language: Invalid enum value. Expected 'kuery' | 'lucene', received 'something-made-up'"`
);
});

View file

@ -237,7 +237,7 @@ describe('Create rule route', () => {
});
const result = await server.validate(request);
expect(result.badRequest).toHaveBeenCalledWith(
'type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "saved_query", saved_id: Required, type: Invalid literal value, expected "threshold", and 18 more'
'response_actions.0.action_type_id: Invalid literal value, expected ".osquery", response_actions.0.params.command: Invalid literal value, expected "isolate"'
);
});
});

View file

@ -284,7 +284,7 @@ describe('Update rule route', () => {
});
const result = await server.validate(request);
expect(result.badRequest).toHaveBeenCalledWith(
'type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "saved_query", saved_id: Required, type: Invalid literal value, expected "threshold", and 18 more'
`response_actions.0.action_type_id: Invalid literal value, expected \".osquery\", response_actions.0.params.command: Invalid literal value, expected \"isolate\"`
);
});
});

View file

@ -283,7 +283,7 @@ describe('create_rules_stream_from_ndjson', () => {
immutable: false,
});
expect(resultOrError[1].message).toContain(
'name: Required, description: Required, risk_score: Required, severity: Required, rule_id: Required, and 25 more'
`name: Required, description: Required, risk_score: Required, severity: Required, type: Invalid discriminator value. Expected 'eql' | 'query' | 'saved_query' | 'threshold' | 'threat_match' | 'machine_learning' | 'new_terms' | 'esql', and 1 more`
);
expect(resultOrError[2]).toEqual({
rule_id: 'rule-2',

View file

@ -95,7 +95,7 @@ describe('validate', () => {
delete ruleAlert.name;
expect(() => {
transformValidate(ruleAlert);
}).toThrowError('Invalid input');
}).toThrowError('Required');
});
});
@ -113,8 +113,7 @@ describe('validate', () => {
const validatedOrError = transformValidateBulkError('rule-1', ruleAlert);
const expected: BulkError = {
error: {
message:
'name: Required, type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", name: Required, name: Required, and 22 more',
message: 'name: Required',
status_code: 500,
},
rule_id: 'rule-1',

View file

@ -7,6 +7,7 @@
import { each, map, some, uniq } from 'lodash';
import { containsDynamicQuery } from '@kbn/osquery-plugin/common/utils/replace_params_query';
import { requiredOptional } from '@kbn/zod-helpers';
import type { ResponseActionAlerts } from './types';
import type { SetupPlugins } from '../../../plugin_contract';
import type { RuleResponseOsqueryAction } from '../../../../common/api/detection_engine/model/rule_response_actions';
@ -32,7 +33,7 @@ export const osqueryResponseAction = (
return osqueryCreateActionService.create({
...rest,
queries,
queries: requiredOptional(queries),
ecs_mapping: ecsMapping,
saved_query_id: savedQueryId,
agent_ids: agentIds,
@ -43,7 +44,7 @@ export const osqueryResponseAction = (
return osqueryCreateActionService.create(
{
...rest,
queries,
queries: requiredOptional(queries),
ecs_mapping: ecsMapping,
saved_query_id: savedQueryId,
agent_ids: alert.agent?.id ? [alert.agent.id] : [],

View file

@ -43,6 +43,7 @@ import {
TIMESTAMP,
} from '@kbn/rule-data-utils';
import { flattenWithPrefix } from '@kbn/securitysolution-rules';
import { requiredOptional } from '@kbn/zod-helpers';
import { createHash } from 'crypto';
@ -229,7 +230,7 @@ export const buildAlert = (
[ALERT_RULE_NAMESPACE_FIELD]: params.namespace,
[ALERT_RULE_NOTE]: params.note,
[ALERT_RULE_REFERENCES]: params.references,
[ALERT_RULE_RISK_SCORE_MAPPING]: params.riskScoreMapping,
[ALERT_RULE_RISK_SCORE_MAPPING]: requiredOptional(params.riskScoreMapping),
[ALERT_RULE_RULE_ID]: params.ruleId,
[ALERT_RULE_RULE_NAME_OVERRIDE]: params.ruleNameOverride,
[ALERT_RULE_SEVERITY_MAPPING]: params.severityMapping,

View file

@ -7,6 +7,7 @@
import { flattenWithPrefix } from '@kbn/securitysolution-rules';
import type * as estypes from '@elastic/elasticsearch/lib/api/types';
import { requiredOptional } from '@kbn/zod-helpers';
import type { BaseHit, SearchTypes } from '../../../../../../common/detection_engine/types';
import type { ConfigType } from '../../../../../config';
@ -92,7 +93,7 @@ export const buildBulkBody = (
riskScoreOverride: buildRiskScoreFromMapping({
eventSource: mergedDoc._source ?? {},
riskScore: completeRule.ruleParams.riskScore,
riskScoreMapping: completeRule.ruleParams.riskScoreMapping,
riskScoreMapping: requiredOptional(completeRule.ruleParams.riskScoreMapping),
}).riskScore,
}
: undefined;

View file

@ -447,7 +447,7 @@ export default ({ getService }: FtrProviderContext): void => {
.expect(400);
expect(body.message).to.eql(
'[request body]: 0.investigation_fields: Expected object, received array, 0.type: Invalid literal value, expected "eql", 0.language: Invalid literal value, expected "eql", 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, and 22 more'
'[request body]: 0.investigation_fields: Expected object, received array'
);
});
});

View file

@ -411,11 +411,7 @@ export default ({ getService }: FtrProviderContext): void => {
expect(body.errors[0]).to.eql({
rule_id: '(unknown id)',
error: {
message:
'type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "query", type: Invalid literal value, expected "saved_query", saved_id: Required, and 14 more',
status_code: 400,
},
error: { status_code: 400, message: 'threshold: Required' },
});
});

View file

@ -566,8 +566,7 @@ export default ({ getService }: FtrProviderContext) => {
expect(body).to.eql({
error: 'Bad Request',
message:
'[request body]: type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "query", type: Invalid literal value, expected "saved_query", saved_id: Required, and 14 more',
message: '[request body]: threshold: Required',
statusCode: 400,
});
});
@ -957,7 +956,7 @@ export default ({ getService }: FtrProviderContext) => {
.expect(400);
expect(body.message).to.eql(
'[request body]: investigation_fields: Expected object, received array, type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, and 22 more'
'[request body]: investigation_fields: Expected object, received array'
);
});

View file

@ -854,7 +854,7 @@ export default ({ getService }: FtrProviderContext) => {
.expect(400);
expect(body.message).to.eql(
'[request body]: 0.investigation_fields: Expected object, received array, 0.type: Invalid literal value, expected "eql", 0.language: Invalid literal value, expected "eql", 0.investigation_fields: Expected object, received array, 0.investigation_fields: Expected object, received array, and 22 more'
'[request body]: 0.investigation_fields: Expected object, received array'
);
});

View file

@ -431,8 +431,7 @@ export default ({ getService }: FtrProviderContext) => {
expect(body).toEqual({
error: 'Bad Request',
message:
'[request body]: type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", type: Invalid literal value, expected "query", type: Invalid literal value, expected "saved_query", saved_id: Required, and 14 more',
message: '[request body]: threshold: Required',
statusCode: 400,
});
});
@ -541,7 +540,7 @@ export default ({ getService }: FtrProviderContext) => {
.expect(400);
expect(body.message).toBe(
'[request body]: investigation_fields: Expected object, received array, type: Invalid literal value, expected "eql", language: Invalid literal value, expected "eql", investigation_fields: Expected object, received array, investigation_fields: Expected object, received array, and 22 more'
'[request body]: investigation_fields: Expected object, received array'
);
});
});