Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split packages #2

Merged
merged 4 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Move tests to corresponding package, cleanup
  • Loading branch information
p10ns11y committed Nov 8, 2024
commit 23b1c27460c7ac2200e3869c0439a3de817169e5
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Dynamic and Adaptable Model Validator Using Zod, Interoperable with OpenAPI

![Coverage Badge](/coverage-badge.svg)

## Overview

`adaptate` is a dynamic and adaptable model validator that leverages the power of Zod for schema validation and is interoperable with OpenAPI. This library allows you to define, validate, and transform schemas seamlessly.
Expand Down
1 change: 1 addition & 0 deletions coverage-badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions jest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ export default {
],
},
extensionsToTreatAsEsm: ['.ts', '.tsx'],
coverageReporters: ['json-summary'],
};
46 changes: 0 additions & 46 deletions packages/core/src/__tests__/check-model.test.ts

This file was deleted.

251 changes: 43 additions & 208 deletions packages/core/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { z } from 'zod';
import { getDereferencedOpenAPIDocument } from '@adaptate/utils/openapi';
import {
makeSchemaRequired,
getDereferencedOpenAPIDocument,
openAPISchemaToZod,
applyConditionalRequirements,
zodToOpenAPISchema,
} from '../';
} from '@adaptate/utils/openapi';
import { makeSchemaRequired, applyConditionalRequirements } from '../';

describe('makeSchemaRequired', () => {
it('should make properties required based on the config', async () => {
Expand Down Expand Up @@ -63,7 +61,6 @@ describe('makeSchemaRequired', () => {
subcategories: [{ items: ['iPhone', 'Samsung Galaxy'] }],
},
type: 'electronics',
warrantyPeriod: '2 years',
};

let invalidDataItems = {
Expand All @@ -80,6 +77,9 @@ describe('makeSchemaRequired', () => {
};

expect(() => transformedSchema.parse(validData)).not.toThrow();

expect(() => baseSchema.parse(invalidDataMissingName)).not.toThrow();

expect(() => transformedSchema.parse(invalidDataMissingName))
.toThrowErrorMatchingInlineSnapshot(`
"[
Expand Down Expand Up @@ -108,6 +108,25 @@ describe('makeSchemaRequired', () => {
]"
`);

// Re transforming the schema with different config
// Here making warrantyPeriod required
expect(() =>
makeSchemaRequired(transformedSchema, {
category: {
warrantyPeriod: true,
},
}).parse(invalidDataMissingName)
).toThrow();

expect(() => baseSchema.parse({})).not.toThrow();
expect(() =>
baseSchema.parse({
category: {
subcategories: [{ items: [] }],
},
})
).not.toThrow();
expect(() => baseSchema.parse(invalidDataItems)).toThrow();
expect(() => transformedSchema.parse(invalidDataItems))
.toThrowErrorMatchingInlineSnapshot(`
"[
Expand Down Expand Up @@ -187,28 +206,28 @@ describe('makeSchemaRequired', () => {

expect(() => anotherTransformedSchema.parse(anotherInValidData)).toThrow();

// let dataLoadedFromYAML = await getDereferencedOpenAPIDocument(
// import.meta.url,
// '../fixtures/base-schema.yml'
// );
// let dataZodSchema = openAPISchemaToZod(
// // @ts-ignore
// dataLoadedFromYAML['components']['schemas']['Category']
// );
let dereferencedOpenAPIDocument = await getDereferencedOpenAPIDocument(
import.meta.url,
'../fixtures/base-schema.yml'
);
let dataZodSchema = openAPISchemaToZod(
// @ts-ignore
dereferencedOpenAPIDocument['components']['schemas']['Category']
);

// let yetAnotherTransformedSchema = makeSchemaRequired(dataZodSchema, config);
let yetAnotherTransformedSchema = makeSchemaRequired(dataZodSchema, config);

// expect(() =>
// yetAnotherTransformedSchema.parse(validData['category'])
// ).not.toThrow();
expect(() =>
yetAnotherTransformedSchema.parse(validData['category'])
).not.toThrow();

// expect(() =>
// yetAnotherTransformedSchema.parse(invalidDataMissingName['category'])
// ).toThrow();
expect(() =>
yetAnotherTransformedSchema.parse(invalidDataMissingName['category'])
).toThrow();

// expect(() =>
// yetAnotherTransformedSchema.parse(invalidDataItems['category'])
// ).toThrow();
expect(() =>
yetAnotherTransformedSchema.parse(invalidDataItems['category'])
).toThrow();
});

it('should handle array of objects at top level', () => {
Expand Down Expand Up @@ -316,143 +335,6 @@ describe('makeSchemaRequired', () => {
});
});

describe('openAPISchemaToZod', () => {
it('should convert OpenAPI string schema to Zod string schema', () => {
let openAPISchema = {
type: 'object',
required: ['name'],
properties: {
name: {
type: 'string',
},
items: {
type: 'array',
items: {
type: 'string',
},
},
},
};
// const openAPISchema = { type: 'string' };
const zodSchema = openAPISchemaToZod(openAPISchema);
// @ts-ignore
expect(zodSchema.shape.name).toBeInstanceOf(z.ZodString);
// @ts-ignore
expect(zodSchema.shape.items).toBeInstanceOf(z.ZodOptional);
// @ts-ignore
expect(zodSchema.shape.items.unwrap()).toBeInstanceOf(z.ZodArray);
expect(
// @ts-ignore
zodSchema.shape.items.unwrap().element.unwrap().unwrap()
).toBeInstanceOf(z.ZodString);
});

it('should convert OpenAPI number schema to Zod number schema', () => {
const openAPISchema = {
type: 'object',
required: ['id'],
properties: {
id: {
type: 'number',
},
},
};
const zodSchema = openAPISchemaToZod(openAPISchema);
// @ts-ignore
expect(zodSchema.shape.id).toBeInstanceOf(z.ZodNumber);
});

it('should convert OpenAPI boolean schema to Zod boolean schema', () => {
const openAPISchema = {
type: 'object',
required: ['enabled'],
properties: {
enabled: {
type: 'boolean',
},
},
};

const zodSchema = openAPISchemaToZod(openAPISchema);
// @ts-ignore
expect(zodSchema.shape.enabled).toBeInstanceOf(z.ZodBoolean);
});

it('should convert OpenAPI array schema to Zod array schema', () => {
const openAPISchema = {
type: 'object',
required: ['products'],
properties: {
products: {
type: 'array',
items: { type: 'string' },
},
},
};

const zodSchema = openAPISchemaToZod(
openAPISchema
) as z.ZodArray<z.ZodString>;

// @ts-ignore
expect(zodSchema.shape.products).toBeInstanceOf(z.ZodArray);
});

it('should convert OpenAPI object schema to Zod object schema', () => {
const openAPISchema = {
type: 'object',
required: ['name', 'age', 'email', 'count'],
properties: {
name: { type: 'string' },
age: { type: 'number' },
email: { type: 'string', format: 'email' },
count: { type: 'integer' },
unknownType: { type: 'unknown' },
},
};
const zodSchema = openAPISchemaToZod(openAPISchema) as z.ZodObject<{
name: z.ZodString;
age: z.ZodNumber;
email: z.ZodString;
count: z.ZodNumber;
unknownType: z.ZodAny;
}>;
expect(zodSchema).toBeInstanceOf(z.ZodObject);
expect(zodSchema.shape.name).toBeInstanceOf(z.ZodString);
expect(zodSchema.shape.age).toBeInstanceOf(z.ZodNumber);
expect(zodSchema.shape.email).toBeInstanceOf(z.ZodString);
expect(zodSchema.shape.count).toBeInstanceOf(z.ZodNumber);
// @ts-ignore
expect(zodSchema.shape.unknownType.unwrap()).toBeInstanceOf(z.ZodAny);
});

it('should convert OpenAPI schema with $ref to another component using openapi-spec-parser', async () => {
let dataLoadedFromYAML = await getDereferencedOpenAPIDocument(
import.meta.url,
'../fixtures/base-schema.yml'
);
let zodSchema = openAPISchemaToZod(
// @ts-ignore
dataLoadedFromYAML.components.schemas.Category
) as z.ZodObject<{
name: z.ZodString;
subcategories: z.ZodArray<
z.ZodObject<{ name: z.ZodString; items: z.ZodArray<z.ZodString> }>
>;
}>;

expect(zodSchema).toBeInstanceOf(z.ZodObject);
expect(zodSchema.shape.name).toBeInstanceOf(z.ZodString);
expect(zodSchema.shape.subcategories).toBeInstanceOf(z.ZodArray);
expect(zodSchema.shape.subcategories.element.shape.name).toBeInstanceOf(
z.ZodString
);
expect(zodSchema.shape.subcategories.element.shape.items).toBeInstanceOf(
z.ZodArray
);
});
});

// Rethink
describe('applyConditionalRequirements', () => {
it('should apply conditional requirements based on the config', () => {
Expand Down Expand Up @@ -512,50 +394,3 @@ describe('applyConditionalRequirements', () => {
expect(result).toBe(schema);
});
});

describe('zodToOpenAPISchema', () => {
it('should convert Zod string schema to OpenAPI string schema', () => {
const zodSchema = z.string();
const openAPISchema = zodToOpenAPISchema(zodSchema);
expect(openAPISchema).toEqual({ type: 'string' });
});

it('should convert Zod number schema to OpenAPI number schema', () => {
const zodSchema = z.number();
const openAPISchema = zodToOpenAPISchema(zodSchema);
expect(openAPISchema).toEqual({ type: 'number' });
});

it('should convert Zod boolean schema to OpenAPI boolean schema', () => {
const zodSchema = z.boolean();
const openAPISchema = zodToOpenAPISchema(zodSchema);
expect(openAPISchema).toEqual({ type: 'boolean' });
});

it('should convert Zod array schema to OpenAPI array schema', () => {
const zodSchema = z.array(z.string());
const openAPISchema = zodToOpenAPISchema(zodSchema);
expect(openAPISchema).toEqual({ type: 'array', items: { type: 'string' } });
});

it('should convert Zod object schema to OpenAPI object schema', () => {
const zodSchema = z.object({
name: z.string(),
age: z.number(),
});
const openAPISchema = zodToOpenAPISchema(zodSchema);
expect(openAPISchema).toEqual({
type: 'object',
properties: {
name: { type: 'string' },
age: { type: 'number' },
},
});
});

it('should handle unsupported Zod schema type', () => {
const zodSchema = z.date();
const openAPISchema = zodToOpenAPISchema(zodSchema);
expect(openAPISchema).toEqual({});
});
});
Loading