Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions x-pack/platform/plugins/shared/cases/common/schema_zod/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { z } from '@kbn/zod/v4';
import { ALLOWED_MIME_TYPES } from '../constants/mime_types';

export interface LimitedSchemaType {
fieldName: string;
min: number;
max: number;
}

export const NonEmptyString = z.string().min(1);

export const limitedStringSchema = ({ fieldName, min, max }: LimitedSchemaType) =>
z.string().superRefine((s, ctx) => {
const trimmed = s.trim();

if (trimmed.length === 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `The ${fieldName} field cannot be an empty string.`,
});
return;
}

if (trimmed.length < min) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `The length of the ${fieldName} is too short. The minimum length is ${min}.`,
});
return;
}

if (trimmed.length > max) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `The length of the ${fieldName} is too long. The maximum length is ${max}.`,
});
}
});

export const limitedArraySchema = <T extends z.ZodTypeAny>({
codec,
fieldName,
min,
max,
}: { codec: T } & LimitedSchemaType) =>
z.array(codec).superRefine((s, ctx) => {
if (s.length < min) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `The length of the field ${fieldName} is too short. Array must be of length >= ${min}.`,
});
return;
}

if (s.length > max) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `The length of the field ${fieldName} is too long. Array must be of length <= ${max}.`,
});
}
});

export const limitedNumberSchema = ({ fieldName, min, max }: LimitedSchemaType) =>
z.number().superRefine((s, ctx) => {
if (s < min) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `The ${fieldName} field cannot be less than ${min}.`,
});
return;
}

if (s > max) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `The ${fieldName} field cannot be more than ${max}.`,
});
}
});

export const paginationSchema = ({ maxPerPage }: { maxPerPage: number }) => {
const pageCoerce = z.union([z.number(), z.string().transform((s) => Number(s))]);
return z.object({
page: pageCoerce.optional(),
perPage: pageCoerce.optional(),
});
};

export const limitedNumberAsIntegerSchema = ({ fieldName }: { fieldName: string }) =>
z.number().superRefine((s, ctx) => {
if (!Number.isSafeInteger(s)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `The ${fieldName} field should be an integer between -(2^53 - 1) and 2^53 - 1, inclusive.`,
});
}
});

export const regexStringSchema = ({
codec,
pattern,
message,
}: {
codec: z.ZodType<string>;
pattern: string;
message: string;
}) =>
codec.superRefine((value, ctx) => {
if (!new RegExp(pattern).test(value)) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message });
}
});

export const mimeTypeString = z.string().superRefine((s, ctx) => {
if (!ALLOWED_MIME_TYPES.includes(s)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `The mime type field value ${s} is not allowed.`,
});
}
});

/**
* Zod equivalent of jsonValueRt — a recursive JSON value type.
*/
export type JsonValue =
| string
| number
| boolean
| null
| JsonValue[]
| { [key: string]: JsonValue };

export const jsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>
z.union([
z.string(),
z.number(),
z.boolean(),
z.null(),
z.array(jsonValueSchema),
z.record(z.string(), jsonValueSchema),
])
);
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ import {
FindAttachmentsQueryParamsRt,
PostFileAttachmentRequestRt,
} from './v1';
import {
AttachmentPatchRequestSchema,
AttachmentRequestSchema,
AttachmentsFindResponseSchema,
BulkCreateAttachmentsRequestSchema,
BulkDeleteFileAttachmentsRequestSchema,
BulkGetAttachmentsRequestSchema,
BulkGetAttachmentsResponseSchema,
FindAttachmentsQueryParamsSchema,
PostFileAttachmentRequestSchema,
} from '../../api_zod/attachment/v1';

describe('Attachments', () => {
describe('BulkDeleteFileAttachmentsRequestRt', () => {
Expand All @@ -46,6 +57,21 @@ describe('Attachments', () => {
right: { ids: ['abc', 'xyz'] },
});
});

it('zod: has expected attributes in request', () => {
const result = BulkDeleteFileAttachmentsRequestSchema.safeParse({ ids: ['abc', 'xyz'] });
expect(result.success).toBe(true);
expect(result.data).toStrictEqual({ ids: ['abc', 'xyz'] });
});

it('zod: strips unknown fields', () => {
const result = BulkDeleteFileAttachmentsRequestSchema.safeParse({
ids: ['abc', 'xyz'],
foo: 'bar',
});
expect(result.success).toBe(true);
expect(result.data).toStrictEqual({ ids: ['abc', 'xyz'] });
});
});

describe('AttachmentRequestRt', () => {
Expand Down Expand Up @@ -73,6 +99,18 @@ describe('Attachments', () => {
});
});

it('zod: has expected attributes in request', () => {
const result = AttachmentRequestSchema.safeParse(defaultRequest);
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});

it('zod: strips unknown fields', () => {
const result = AttachmentRequestSchema.safeParse({ ...defaultRequest, foo: 'bar' });
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});

describe('errors', () => {
describe('commentType: user', () => {
it('throws error when comment is too long', () => {
Expand Down Expand Up @@ -166,6 +204,18 @@ describe('Attachments', () => {
right: defaultRequest,
});
});

it('zod: has expected attributes in request', () => {
const result = AttachmentPatchRequestSchema.safeParse(defaultRequest);
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});

it('zod: strips unknown fields', () => {
const result = AttachmentPatchRequestSchema.safeParse({ ...defaultRequest, foo: 'bar' });
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});
});

describe('AttachmentsFindResponseRt', () => {
Expand Down Expand Up @@ -222,6 +272,18 @@ describe('Attachments', () => {
right: defaultRequest,
});
});

it('zod: has expected attributes in request', () => {
const result = AttachmentsFindResponseSchema.safeParse(defaultRequest);
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});

it('zod: strips unknown fields', () => {
const result = AttachmentsFindResponseSchema.safeParse({ ...defaultRequest, foo: 'bar' });
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});
});

describe('FindAttachmentsQueryParamsRt', () => {
Expand All @@ -248,6 +310,18 @@ describe('Attachments', () => {
right: defaultRequest,
});
});

it('zod: has expected attributes in request', () => {
const result = FindAttachmentsQueryParamsSchema.safeParse(defaultRequest);
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});

it('zod: strips unknown fields', () => {
const result = FindAttachmentsQueryParamsSchema.safeParse({ ...defaultRequest, foo: 'bar' });
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});
});

describe('BulkCreateAttachmentsRequestRt', () => {
Expand Down Expand Up @@ -279,6 +353,20 @@ describe('Attachments', () => {
});
});

it('zod: has expected attributes in request', () => {
const result = BulkCreateAttachmentsRequestSchema.safeParse(defaultRequest);
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});

it('zod: strips unknown fields', () => {
const result = BulkCreateAttachmentsRequestSchema.safeParse([
{ comment: 'Solve this fast!', type: AttachmentType.user, owner: 'cases', foo: 'bar' },
]);
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});

describe('errors', () => {
it(`throws error when attachments are more than ${MAX_BULK_CREATE_ATTACHMENTS}`, () => {
const comment = {
Expand Down Expand Up @@ -319,6 +407,21 @@ describe('Attachments', () => {
right: { ids: ['abc', 'xyz'] },
});
});

it('zod: has expected attributes in request', () => {
const result = BulkGetAttachmentsRequestSchema.safeParse({ ids: ['abc', 'xyz'] });
expect(result.success).toBe(true);
expect(result.data).toStrictEqual({ ids: ['abc', 'xyz'] });
});

it('zod: strips unknown fields', () => {
const result = BulkGetAttachmentsRequestSchema.safeParse({
ids: ['abc', 'xyz'],
foo: 'bar',
});
expect(result.success).toBe(true);
expect(result.data).toStrictEqual({ ids: ['abc', 'xyz'] });
});
});

describe('BulkGetAttachmentsResponseRt', () => {
Expand Down Expand Up @@ -393,6 +496,18 @@ describe('Attachments', () => {
right: defaultRequest,
});
});

it('zod: has expected attributes in request', () => {
const result = BulkGetAttachmentsResponseSchema.safeParse(defaultRequest);
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});

it('zod: strips unknown fields', () => {
const result = BulkGetAttachmentsResponseSchema.safeParse({ ...defaultRequest, foo: 'bar' });
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});
});

describe('PostFileAttachmentRequestRt', () => {
Expand All @@ -419,6 +534,18 @@ describe('Attachments', () => {
});
});

it('zod: has expected attributes in request', () => {
const result = PostFileAttachmentRequestSchema.safeParse(defaultRequest);
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});

it('zod: strips unknown fields', () => {
const result = PostFileAttachmentRequestSchema.safeParse({ ...defaultRequest, foo: 'bar' });
expect(result.success).toBe(true);
expect(result.data).toStrictEqual(defaultRequest);
});

describe('errors', () => {
it('throws an error when the filename is too long', () => {
const longFilename = 'x'.repeat(MAX_FILENAME_LENGTH + 1);
Expand Down
Loading
Loading