Merge pull request #334 from buster-so/big-nate/bus-1174-make-zod-or-valibot-types-for-api-protocol-types

Big nate/bus 1174 make zod or valibot types for api protocol types
This commit is contained in:
Nate Kelley 2025-05-28 13:24:30 -06:00 committed by GitHub
commit 49adaa1f7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 1969 additions and 782 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "buster_server"
version = "0.1.12"
version = "0.1.13"
edition = "2021"
default-run = "buster_server"

View File

@ -1,6 +1,6 @@
[package]
name = "buster-cli"
version = "0.1.12"
version = "0.1.13"
edition = "2021"
build = "build.rs"

View File

@ -1,7 +1,7 @@
{
"api_tag": "api/v0.1.12", "api_version": "0.1.12"
"api_tag": "api/v0.1.13", "api_version": "0.1.13"
,
"web_tag": "web/v0.1.12", "web_version": "0.1.12"
"web_tag": "web/v0.1.13", "web_version": "0.1.13"
,
"cli_tag": "cli/v0.1.12", "cli_version": "0.1.12"
"cli_tag": "cli/v0.1.13", "cli_version": "0.1.13"
}

View File

@ -12,6 +12,7 @@ You are a TypeScript expert with deep knowledge of the language's features and b
- Use TypeScript for all new code
- Prefer functional components and hooks over class components
- Use proper TypeScript types for all variables and functions
- When importing types, ALWAYS append the type with the keyword type in the import (in order to minimize build size)
## Best Practices
- Try to use components defined in my @/components/ui folder.

2311
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "web",
"version": "0.1.12",
"version": "0.1.13",
"private": true,
"scripts": {
"dev": "next dev --turbo",
@ -25,30 +25,30 @@
"@dnd-kit/sortable": "^10.0.0",
"@faker-js/faker": "^9.7.0",
"@monaco-editor/react": "^4.7.0",
"@radix-ui/react-avatar": "^1.1.9",
"@radix-ui/react-checkbox": "^1.3.1",
"@radix-ui/react-collapsible": "^1.1.10",
"@radix-ui/react-context-menu": "^2.2.14",
"@radix-ui/react-dialog": "^1.1.13",
"@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-popover": "^1.1.13",
"@radix-ui/react-scroll-area": "^1.2.8",
"@radix-ui/react-select": "^2.2.4",
"@radix-ui/react-separator": "^1.1.6",
"@radix-ui/react-slider": "^1.3.4",
"@radix-ui/react-switch": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.11",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-checkbox": "^1.3.2",
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-context-menu": "^2.2.15",
"@radix-ui/react-dialog": "^1.1.14",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slider": "^1.3.5",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-tooltip": "^1.2.6",
"@supabase/ssr": "^0.6.1",
"@supabase/supabase-js": "^2.49.4",
"@tanstack/query-sync-storage-persister": "^5.76.1",
"@tanstack/react-form": "^1.11.1",
"@tanstack/react-query": "^5.76.1",
"@tanstack/react-query-devtools": "^5.76.1",
"@tanstack/react-query-persist-client": "^5.76.1",
"@supabase/supabase-js": "^2.49.8",
"@tanstack/query-sync-storage-persister": "^5.77.2",
"@tanstack/react-form": "^1.12.0",
"@tanstack/react-query": "^5.77.2",
"@tanstack/react-query-devtools": "^5.77.2",
"@tanstack/react-query-persist-client": "^5.77.2",
"@tanstack/react-table": "^8.21.3",
"@tanstack/react-virtual": "^3.13.8",
"@tanstack/react-virtual": "^3.13.9",
"@types/jest": "^29.5.14",
"@types/prettier": "^2.7.3",
"@types/react-color": "^3.0.13",
@ -65,7 +65,7 @@
"dom-to-image": "^2.6.0",
"email-validator": "^2.0.4",
"font-color-contrast": "^11.1.0",
"framer-motion": "^12.11.3",
"framer-motion": "^12.15.0",
"intersection-observer": "^0.12.2",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
@ -73,14 +73,14 @@
"js-yaml": "^4.1.0",
"jwt-decode": "^4.0.0",
"lodash": "^4.17.21",
"monaco-sql-languages": "^0.14.0",
"monaco-sql-languages": "^0.15.0",
"monaco-yaml": "^5.4.0",
"mutative": "^1.1.0",
"mutative": "^1.2.0",
"next": "14.2.28",
"next-themes": "^0.4.6",
"papaparse": "^5.5.2",
"papaparse": "^5.5.3",
"pluralize": "^8.0.0",
"posthog-js": "^1.242.1",
"posthog-js": "^1.248.0",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"react": "^18",
@ -95,12 +95,12 @@
"remark-gfm": "^4.0.1",
"sonner": "^2.0.3",
"tailwind-merge": "^3.3.0",
"ts-jest": "^29.3.3",
"ts-jest": "^29.3.4",
"use-context-selector": "^2.0.0",
"utility-types": "^3.11.0",
"valibot": "^1.1.0",
"virtua": "^0.41.0",
"zustand": "^5.0.4"
"virtua": "^0.41.2",
"zod": "^3.25.32",
"zustand": "^5.0.5"
},
"devDependencies": {
"@chromatic-com/storybook": "^3.2.6",
@ -108,19 +108,19 @@
"@next/bundle-analyzer": "^15.3.2",
"@playwright/test": "^1.52.0",
"@storybook/addon-controls": "^8.6.12",
"@storybook/addon-essentials": "^8.6.12",
"@storybook/addon-interactions": "^8.6.12",
"@storybook/blocks": "^8.6.12",
"@storybook/nextjs": "^8.6.12",
"@storybook/react": "^8.6.12",
"@storybook/test": "^8.6.12",
"@tailwindcss/postcss": "4.1.6",
"@storybook/addon-essentials": "^8.6.14",
"@storybook/addon-interactions": "^8.6.14",
"@storybook/blocks": "^8.6.14",
"@storybook/nextjs": "^8.6.14",
"@storybook/react": "^8.6.14",
"@storybook/test": "^8.6.14",
"@tailwindcss/postcss": "4.1.8",
"@testing-library/react": "^16.3.0",
"@types/canvas-confetti": "^1.9.0",
"@types/dotenv": "^8.2.3",
"@types/js-cookie": "^3.0.6",
"@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.17.16",
"@types/lodash": "^4.17.17",
"@types/node": "^20",
"@types/papaparse": "^5.3.16",
"@types/pluralize": "^0.0.33",
@ -134,9 +134,9 @@
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-storybook": "^0.12.0",
"msw-storybook-addon": "^2.0.4",
"sass": "^1.88.0",
"sass": "^1.89.0",
"tailwind-scrollbar": "^4.0.2",
"tailwindcss": "4.1.6",
"tailwindcss": "4.1.8",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5"
},

View File

@ -1,6 +1,21 @@
export interface BusterApiKeyListItem {
id: string;
owner_id: string;
owner_email: string;
created_at: string;
}
import { z } from 'zod/v4-mini';
export const BusterApiKeyListItemSchema = z.object({
id: z.string(),
owner_id: z.string('Owner ID is required'),
owner_email: z.string('Owner email is required'),
created_at: z.string('Created at is required')
});
export type BusterApiKeyListItem = z.infer<typeof BusterApiKeyListItemSchema>;
// API Response schemas
export const GetApiKeysResponseSchema = z.object({
api_keys: z.array(BusterApiKeyListItemSchema)
});
export type GetApiKeysResponse = z.infer<typeof GetApiKeysResponseSchema>;
export const CreateApiKeyResponseSchema = z.object({
api_key: z.string('API Key is required')
});

View File

@ -1,4 +1,3 @@
import * as v from 'valibot';
import {
DataSourceSchema,
DataSourceTypes,
@ -11,20 +10,18 @@ import {
SQLServerCredentialsSchema,
SnowflakeCredentials
} from './interfaces';
import { z } from 'zod/v4-mini';
// Helper function to test validation
const testValidation = (
schema: any,
schema: z.ZodMiniObject<any>,
value: any
): { success: boolean; issues?: v.ValiError<any>['issues'] } => {
try {
v.parse(schema, value);
): { success: boolean; issues?: z.core.$ZodIssue[] } => {
const result = schema.safeParse(value);
if (result.success) {
return { success: true };
} catch (error) {
if (error instanceof v.ValiError) {
return { success: false, issues: error.issues };
}
throw error;
} else {
return { success: false, issues: result.error.issues };
}
};
@ -134,7 +131,10 @@ describe('DataSourceSchema', () => {
const result = testValidation(DataSourceSchema, invalidPortDataSource);
expect(result.success).toBe(false);
expect(result.issues).toBeDefined();
expect(result.issues![0].message).toBe('Port must be greater than 0');
expect(result.issues![0].message).toBe('Invalid input');
const testMessage = result.issues![0] as z.core.$ZodIssueInvalidUnion;
expect(testMessage.errors[0][0].message).toBe('Port must be greater than 0');
});
test('should fail validation with missing required fields', () => {

View File

@ -1,4 +1,4 @@
import * as v from 'valibot';
import { z } from 'zod/v4-mini';
export enum DataSourceStatus {
ACTIVE = 'active',
@ -56,145 +56,148 @@ export enum DataSourceEnvironment {
development = 'development'
}
export const PostgresCredentialsSchema = v.object({
name: v.pipe(v.string(), v.minLength(3, 'Name must be at least 3 characters')),
type: v.union([v.literal('postgres'), v.literal('supabase')]),
host: v.pipe(v.string(), v.minLength(1, 'Host must not be empty')),
port: v.pipe(
v.number(),
v.minValue(1, 'Port must be greater than 0'),
v.maxValue(65535, 'Port must be less than or equal to 65535')
),
username: v.pipe(v.string(), v.minLength(1, 'Username must not be empty')),
password: v.pipe(v.string(), v.minLength(1, 'Password must not be empty')),
default_database: v.pipe(v.string(), v.minLength(1, 'Database must not be empty')), // postgres
default_schema: v.pipe(v.string(), v.minLength(1, 'Schema must not be empty')) // public
export const PostgresCredentialsSchema = z.object({
name: z.string().check(z.minLength(3, 'Name must be at least 3 characters')),
type: z.union([z.literal('postgres'), z.literal('supabase')]),
host: z.string().check(z.minLength(1, 'Host must not be empty')),
port: z
.number()
.check(
z.gte(1, 'Port must be greater than 0'),
z.lte(65535, 'Port must be less than or equal to 65535')
),
username: z.string().check(z.minLength(1, 'Username must not be empty')),
password: z.string().check(z.minLength(1, 'Password must not be empty')),
default_database: z.string().check(z.minLength(1, 'Database must not be empty')), // postgres
default_schema: z.string().check(z.minLength(1, 'Schema must not be empty')) // public
});
export type PostgresCredentials = v.InferOutput<typeof PostgresCredentialsSchema>;
export type PostgresCredentials = z.infer<typeof PostgresCredentialsSchema>;
export const MySQLCredentialsSchema = v.object({
name: v.pipe(v.string(), v.minLength(1, 'Name must not be empty')),
type: v.union([v.literal('mysql'), v.literal('mariadb')]),
host: v.pipe(v.string(), v.minLength(1, 'Host must not be empty')),
port: v.pipe(
v.number(),
v.minValue(1, 'Port must be greater than 0'),
v.maxValue(65535, 'Port must be less than or equal to 65535')
),
username: v.pipe(v.string(), v.minLength(1, 'Username must not be empty')),
password: v.pipe(v.string(), v.minLength(1, 'Password must not be empty')),
default_database: v.pipe(v.string(), v.minLength(1, 'Database must not be empty'))
export const MySQLCredentialsSchema = z.object({
name: z.string().check(z.minLength(1, 'Name must not be empty')),
type: z.union([z.literal('mysql'), z.literal('mariadb')]),
host: z.string().check(z.minLength(1, 'Host must not be empty')),
port: z
.number()
.check(
z.gte(1, 'Port must be greater than 0'),
z.lte(65535, 'Port must be less than or equal to 65535')
),
username: z.string().check(z.minLength(1, 'Username must not be empty')),
password: z.string().check(z.minLength(1, 'Password must not be empty')),
default_database: z.string().check(z.minLength(1, 'Database must not be empty'))
});
export type MySQLCredentials = v.InferOutput<typeof MySQLCredentialsSchema>;
export type MySQLCredentials = z.infer<typeof MySQLCredentialsSchema>;
export const BigQueryCredentialsSchema = v.object({
name: v.pipe(v.string(), v.minLength(1, 'Name must not be empty')),
type: v.literal('bigquery'),
service_role_key: v.pipe(v.string(), v.minLength(1, 'Service role key must not be empty')),
default_project_id: v.pipe(v.string(), v.minLength(1, 'Project ID must not be empty')),
default_dataset_id: v.pipe(v.string(), v.minLength(1, 'Dataset ID must not be empty'))
export const BigQueryCredentialsSchema = z.object({
name: z.string().check(z.minLength(1, 'Name must not be empty')),
type: z.literal('bigquery'),
service_role_key: z.string().check(z.minLength(1, 'Service role key must not be empty')),
default_project_id: z.string().check(z.minLength(1, 'Project ID must not be empty')),
default_dataset_id: z.string().check(z.minLength(1, 'Dataset ID must not be empty'))
});
export type BigQueryCredentials = v.InferOutput<typeof BigQueryCredentialsSchema>;
export type BigQueryCredentials = z.infer<typeof BigQueryCredentialsSchema>;
export const RedshiftCredentialsSchema = v.object({
name: v.pipe(v.string(), v.minLength(1, 'Name must not be empty')),
type: v.literal('redshift'),
host: v.pipe(v.string(), v.minLength(1, 'Host must not be empty')),
port: v.pipe(
v.number(),
v.minValue(1, 'Port must be greater than 0'),
v.maxValue(65535, 'Port must be less than or equal to 65535')
),
username: v.pipe(v.string(), v.minLength(1, 'Username must not be empty')),
password: v.pipe(v.string(), v.minLength(1, 'Password must not be empty')),
default_database: v.pipe(v.string(), v.minLength(1, 'Database must not be empty')),
default_schema: v.pipe(v.string(), v.minLength(1, 'Schema must not be empty'))
export const RedshiftCredentialsSchema = z.object({
name: z.string().check(z.minLength(1, 'Name must not be empty')),
type: z.literal('redshift'),
host: z.string().check(z.minLength(1, 'Host must not be empty')),
port: z
.number()
.check(
z.gte(1, 'Port must be greater than 0'),
z.lte(65535, 'Port must be less than or equal to 65535')
),
username: z.string().check(z.minLength(1, 'Username must not be empty')),
password: z.string().check(z.minLength(1, 'Password must not be empty')),
default_database: z.string().check(z.minLength(1, 'Database must not be empty')),
default_schema: z.string().check(z.minLength(1, 'Schema must not be empty'))
});
export type RedshiftCredentials = v.InferOutput<typeof RedshiftCredentialsSchema>;
export type RedshiftCredentials = z.infer<typeof RedshiftCredentialsSchema>;
export const SnowflakeCredentialsSchema = v.object({
name: v.pipe(v.string(), v.minLength(1, 'Name must not be empty')),
type: v.literal('snowflake'),
account_id: v.pipe(v.string(), v.minLength(1, 'Account ID must not be empty')),
warehouse_id: v.pipe(v.string(), v.minLength(1, 'Warehouse ID must not be empty')),
username: v.pipe(v.string(), v.minLength(1, 'Username must not be empty')),
password: v.pipe(v.string(), v.minLength(1, 'Password must not be empty')),
role: v.nullable(v.string()),
default_database: v.pipe(v.string(), v.minLength(1, 'Database must not be empty')),
default_schema: v.pipe(
v.string(),
v.minLength(1, 'Schema must not be empty'),
v.toUpperCase(),
v.custom((value) => value === String(value).toUpperCase(), 'Must be all uppercase')
export const SnowflakeCredentialsSchema = z.object({
name: z.string().check(z.minLength(1, 'Name must not be empty')),
type: z.literal('snowflake'),
account_id: z.string().check(z.minLength(1, 'Account ID must not be empty')),
warehouse_id: z.string().check(z.minLength(1, 'Warehouse ID must not be empty')),
username: z.string().check(z.minLength(1, 'Username must not be empty')),
password: z.string().check(z.minLength(1, 'Password must not be empty')),
role: z.nullable(z.string()),
default_database: z.string().check(z.minLength(1, 'Database must not be empty')),
default_schema: z.string().check(
z.minLength(1, 'Schema must not be empty'),
z.toUpperCase(),
z.refine((val) => val === val.toUpperCase(), 'Must be all uppercase')
)
});
export type SnowflakeCredentials = v.InferOutput<typeof SnowflakeCredentialsSchema>;
export type SnowflakeCredentials = z.infer<typeof SnowflakeCredentialsSchema>;
export const DatabricksCredentialsSchema = v.object({
name: v.string(),
type: v.literal('databricks'),
host: v.string(),
api_key: v.string('API key is required'),
warehouse_id: v.string('Warehouse ID is required'),
default_catalog: v.string(),
default_schema: v.string()
export const DatabricksCredentialsSchema = z.object({
name: z.string(),
type: z.literal('databricks'),
host: z.string(),
api_key: z.string().check(z.refine((val) => val.length > 0, 'API key is required')),
warehouse_id: z.string().check(z.refine((val) => val.length > 0, 'Warehouse ID is required')),
default_catalog: z.string(),
default_schema: z.string()
});
export type DatabricksCredentials = v.InferOutput<typeof DatabricksCredentialsSchema>;
export type DatabricksCredentials = z.infer<typeof DatabricksCredentialsSchema>;
export const SQLServerCredentialsSchema = v.object({
name: v.string(),
type: v.literal('sqlserver'),
host: v.string(),
port: v.pipe(
v.number(),
v.minValue(1, 'Port must be greater than 0'),
v.maxValue(65535, 'Port must be less than or equal to 65535')
),
username: v.pipe(v.string(), v.minLength(1, 'Username must not be empty')),
password: v.pipe(v.string(), v.minLength(1, 'Password must not be empty')),
default_database: v.string(),
default_schema: v.string()
export const SQLServerCredentialsSchema = z.object({
name: z.string().check(z.minLength(1, 'Name must not be empty')),
type: z.literal('sqlserver'),
host: z.string().check(z.minLength(1, 'Host must not be empty')),
port: z
.number()
.check(
z.gte(1, 'Port must be greater than 0'),
z.lte(65535, 'Port must be less than or equal to 65535')
),
username: z.string().check(z.minLength(1, 'Username must not be empty')),
password: z.string().check(z.minLength(1, 'Password must not be empty')),
default_database: z.string().check(z.minLength(1, 'Database must not be empty')),
default_schema: z.string().check(z.minLength(1, 'Schema must not be empty'))
});
export type SQLServerCredentials = v.InferOutput<typeof SQLServerCredentialsSchema>;
export type SQLServerCredentials = z.infer<typeof SQLServerCredentialsSchema>;
export const DataSetSchema = v.object({
id: v.string('Dataset ID is required'),
name: v.string('Dataset name is required')
export const DataSetSchema = z.object({
id: z.string().check(z.refine((val) => val.length > 0, 'Dataset ID is required')),
name: z.string().check(z.refine((val) => val.length > 0, 'Dataset name is required'))
});
export const CreatedBySchema = v.object({
email: v.string('Email is required'),
id: v.string('User ID is required'),
name: v.string('User name is required')
export const CreatedBySchema = z.object({
email: z.string().check(z.refine((val) => val.length > 0, 'Email is required')),
id: z.string().check(z.refine((val) => val.length > 0, 'User ID is required')),
name: z.string().check(z.refine((val) => val.length > 0, 'User name is required'))
});
export const DataSourceSchema = v.object({
created_at: v.pipe(v.string()),
export const DataSourceSchema = z.object({
created_at: z.string(),
created_by: CreatedBySchema,
credentials: v.union([
v.omit(PostgresCredentialsSchema, ['name']),
v.omit(MySQLCredentialsSchema, ['name']),
v.omit(BigQueryCredentialsSchema, ['name']),
v.omit(RedshiftCredentialsSchema, ['name']),
v.omit(SnowflakeCredentialsSchema, ['name']),
v.omit(DatabricksCredentialsSchema, ['name']),
v.omit(SQLServerCredentialsSchema, ['name'])
credentials: z.union([
z.omit(PostgresCredentialsSchema, { name: true }),
z.omit(MySQLCredentialsSchema, { name: true }),
z.omit(BigQueryCredentialsSchema, { name: true }),
z.omit(RedshiftCredentialsSchema, { name: true }),
z.omit(SnowflakeCredentialsSchema, { name: true }),
z.omit(DatabricksCredentialsSchema, { name: true }),
z.omit(SQLServerCredentialsSchema, { name: true })
]),
data_sets: v.array(DataSetSchema),
id: v.string('Data source ID is required'),
name: v.string('Data source name is required'),
type: v.enum(DataSourceTypes),
updated_at: v.pipe(v.string())
data_sets: z.array(DataSetSchema),
id: z.string().check(z.refine((val) => val.length > 0, 'Data source ID is required')),
name: z.string().check(z.refine((val) => val.length > 0, 'Data source name is required')),
type: z.enum(DataSourceTypes),
updated_at: z.string()
});
export type DataSource = v.InferOutput<typeof DataSourceSchema>;
export type DataSource = z.infer<typeof DataSourceSchema>;
export interface DataSourceListItem {
name: string;

View File

@ -1,18 +1,58 @@
import mainApi from '@/api/buster_rest/instances';
import type { BusterApiKeyListItem } from '@/api/asset_interfaces/api_keys';
import {
BusterApiKeyListItemSchema,
GetApiKeysResponseSchema,
CreateApiKeyResponseSchema
} from '@/api/asset_interfaces/api_keys';
// Get API Keys
export const getApiKeys = async () => {
return mainApi.get<{ api_keys: BusterApiKeyListItem[] }>(`/api_keys`).then((res) => res.data);
const response = await mainApi.get(`/api_keys`);
const result = GetApiKeysResponseSchema.safeParse(response.data);
if (!result.success) {
console.error('API Keys validation error:', result.error.issues);
throw new Error(
`Invalid API response format for getApiKeys: ${result.error.issues.map((e) => e.message).join(', ')}`
);
}
return result.data;
};
// Create API Key
export const createApiKey = async (name: string) => {
return mainApi.post<{ api_key: string }>(`/api_keys`, { name }).then((res) => res.data);
const response = await mainApi.post(`/api_keys`, { name });
const result = CreateApiKeyResponseSchema.safeParse(response.data);
if (!result.success) {
console.error('Create API Key validation error:', result.error.issues);
throw new Error(
`Invalid API response format for createApiKey: ${result.error.issues.map((e) => e.message).join(', ')}`
);
}
return result.data;
};
// Delete API Key
export const deleteApiKey = async (id: string) => {
return mainApi.delete(`/api_keys/${id}`).then((res) => res.data);
const response = await mainApi.delete(`/api_keys/${id}`);
return response.data;
};
// Get Single API Key
export const getApiKey = async (id: string) => {
return mainApi.get<BusterApiKeyListItem>(`/api_keys/${id}`).then((res) => res.data);
const response = await mainApi.get(`/api_keys/${id}`);
const result = BusterApiKeyListItemSchema.safeParse(response.data);
if (!result.success) {
console.error('Get API Key validation error:', result.error.issues);
throw new Error(
`Invalid API response format for getApiKey: ${result.error.issues.map((e) => e.message).join(', ')}`
);
}
return result.data;
};

View File

@ -1,4 +1,3 @@
import * as v from 'valibot';
import { DataSource, DataSourceSchema, DataSourceTypes } from '@/api/asset_interfaces/datasources';
import { getDatasource } from './requests';
import mainApi from '../instances';
@ -11,9 +10,12 @@ jest.mock('../instances', () => ({
}
}));
jest.mock('valibot', () => ({
...jest.requireActual('valibot'),
parse: jest.fn().mockImplementation((schema, data) => data)
// Mock the DataSourceSchema
jest.mock('@/api/asset_interfaces/datasources', () => ({
...jest.requireActual('@/api/asset_interfaces/datasources'),
DataSourceSchema: {
parse: jest.fn()
}
}));
describe('data_source requests', () => {
@ -51,11 +53,13 @@ describe('data_source requests', () => {
data: mockDataSource
});
// Setup schema validation mock
(DataSourceSchema.parse as jest.Mock).mockReturnValue(mockDataSource);
const result = await getDatasource('test-id');
expect(true).toBe(true);
expect(mainApi.get).toHaveBeenCalledWith('/data_sources/test-id');
expect(v.parse).toHaveBeenCalledWith(DataSourceSchema, mockDataSource);
expect(DataSourceSchema.parse).toHaveBeenCalledWith(mockDataSource);
expect(result).toEqual(mockDataSource);
});
@ -77,14 +81,14 @@ describe('data_source requests', () => {
// Setup validation error
const validationError = new Error('Validation failed');
jest.spyOn(v, 'parse').mockImplementation(() => {
(DataSourceSchema.parse as jest.Mock).mockImplementation(() => {
throw validationError;
});
// Call and expect error
await expect(getDatasource('test-id')).rejects.toThrow('Validation failed');
expect(mainApi.get).toHaveBeenCalledWith('/data_sources/test-id');
expect(v.parse).toHaveBeenCalled();
expect(DataSourceSchema.parse).toHaveBeenCalled();
});
});
});

View File

@ -10,7 +10,6 @@ import type {
SQLServerCredentials
} from '@/api/asset_interfaces/datasources';
import { DataSourceSchema } from '@/api/asset_interfaces/datasources';
import * as v from 'valibot';
import mainApi from '../instances';
import { serverFetch } from '@/api/createServerInstance';
@ -21,13 +20,13 @@ export const listDatasources = async () => {
export const getDatasource = async (id: string) => {
return await mainApi
.get<DataSource>(`/data_sources/${id}`)
.then((res) => v.parse(DataSourceSchema, res.data));
.then((res) => DataSourceSchema.parse(res.data));
};
export const getDatasource_server = async (id: string) => {
const response = await serverFetch<DataSource>(`/data_sources/${id}`);
// Validate response with DataSourceSchema
return v.parse(DataSourceSchema, response);
return DataSourceSchema.parse(response);
};
export const deleteDatasource = async (id: string) => {