Skip to content

Commit

Permalink
Support .base64() string contentEncoding (#254)
Browse files Browse the repository at this point in the history
  • Loading branch information
samchungy authored Apr 20, 2024
1 parent 82fcd7b commit 4219200
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 16 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -541,9 +541,10 @@ For example in `z.string().nullable()` will be rendered differently
- ZodSet
- Treated as an array with `uniqueItems` (you may need to add a pre-process)
- ZodString
- `format` mapping for `.url()`, `.uuid()`, `.email()`, `.datetime()`
- `format` mapping for `.url()`, `.uuid()`, `.email()`, `.datetime()`, `.date()`, `.time()`, `.duration()`
- `minLength`/`maxLength` mapping for `.length()`, `.min()`, `.max()`
- `pattern` mapping for `.regex()`, `.startsWith()`, `.endsWith()`, `.includes()`
- `contentEncoding` mapping for `.base64()` for OpenAPI 3.1.0+
- ZodTuple
- `items` mapping for `.rest()`
- `prefixItems` mapping for OpenAPI 3.1.0+
Expand Down
2 changes: 1 addition & 1 deletion src/create/schema/parsers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const createSchemaSwitch = <
}

if (isZodType(zodSchema, 'ZodString')) {
return createStringSchema(zodSchema);
return createStringSchema(zodSchema, state);
}

if (isZodType(zodSchema, 'ZodNumber')) {
Expand Down
58 changes: 46 additions & 12 deletions src/create/schema/parsers/string.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { type ZodString, z } from 'zod';

import type { Schema } from '..';
import { extendZodWithOpenApi } from '../../../extendZod';
import {
createOutputOpenapi3State,
createOutputState,
} from '../../../testing/state';

import { createStringSchema } from './string';

Expand All @@ -18,7 +22,7 @@ describe('createStringSchema', () => {

const schema = z.string();

const result = createStringSchema(schema);
const result = createStringSchema(schema, createOutputState());

expect(result).toStrictEqual(expected);
});
Expand All @@ -33,7 +37,7 @@ describe('createStringSchema', () => {
};
const schema = z.string().regex(/^hello/);

const result = createStringSchema(schema);
const result = createStringSchema(schema, createOutputState());

expect(result).toStrictEqual(expected);
});
Expand All @@ -48,7 +52,7 @@ describe('createStringSchema', () => {
};
const schema = z.string().startsWith('hello');

const result = createStringSchema(schema);
const result = createStringSchema(schema, createOutputState());

expect(result).toStrictEqual(expected);
});
Expand All @@ -63,7 +67,7 @@ describe('createStringSchema', () => {
};
const schema = z.string().endsWith('hello');

const result = createStringSchema(schema);
const result = createStringSchema(schema, createOutputState());

expect(result).toStrictEqual(expected);
});
Expand All @@ -78,7 +82,7 @@ describe('createStringSchema', () => {
};
const schema = z.string().includes('hello');

const result = createStringSchema(schema);
const result = createStringSchema(schema, createOutputState());

expect(result).toStrictEqual(expected);
});
Expand All @@ -93,7 +97,7 @@ describe('createStringSchema', () => {
};
const schema = z.string().includes('hello', { position: 5 });

const result = createStringSchema(schema);
const result = createStringSchema(schema, createOutputState());

expect(result).toStrictEqual(expected);
});
Expand All @@ -108,7 +112,7 @@ describe('createStringSchema', () => {
};
const schema = z.string().includes('hello', { position: 0 });

const result = createStringSchema(schema);
const result = createStringSchema(schema, createOutputState());

expect(result).toStrictEqual(expected);
});
Expand Down Expand Up @@ -146,7 +150,7 @@ describe('createStringSchema', () => {
.regex(/^foo/)
.regex(/foo$/);

const result = createStringSchema(schema);
const result = createStringSchema(schema, createOutputState());

expect(result).toStrictEqual(expected);
});
Expand All @@ -162,7 +166,7 @@ describe('createStringSchema', () => {
};
const schema = z.string().min(0).max(1);

const result = createStringSchema(schema);
const result = createStringSchema(schema, createOutputState());

expect(result).toStrictEqual(expected);
});
Expand All @@ -178,7 +182,7 @@ describe('createStringSchema', () => {

const schema = z.string().nonempty();

const result = createStringSchema(schema);
const result = createStringSchema(schema, createOutputState());

expect(result).toStrictEqual(expected);
});
Expand All @@ -194,7 +198,7 @@ describe('createStringSchema', () => {
};
const schema = z.string().length(1);

const result = createStringSchema(schema);
const result = createStringSchema(schema, createOutputState());

expect(result).toStrictEqual(expected);
});
Expand All @@ -218,8 +222,38 @@ describe('createStringSchema', () => {
format,
},
};
const result = createStringSchema(zodString);
const result = createStringSchema(zodString, createOutputState());
expect(result).toStrictEqual(expected);
},
);

it('supports contentEncoding in 3.1.0', () => {
const expected: Schema = {
type: 'schema',
schema: {
type: 'string',
contentEncoding: 'base64',
},
};

const result = createStringSchema(z.string().base64(), createOutputState());

expect(result).toStrictEqual(expected);
});

it('does not support contentEncoding in 3.0.0', () => {
const expected: Schema = {
type: 'schema',
schema: {
type: 'string',
},
};

const result = createStringSchema(
z.string().base64(),
createOutputOpenapi3State(),
);

expect(result).toStrictEqual(expected);
});
});
23 changes: 21 additions & 2 deletions src/create/schema/parsers/string.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import type { ZodString, ZodStringCheck } from 'zod';

import type { Schema } from '..';
import type { Schema, SchemaState } from '..';
import { satisfiesVersion } from '../../../openapi';
import type { oas31 } from '../../../openapi3-ts/dist';

export const createStringSchema = (zodString: ZodString): Schema => {
export const createStringSchema = (
zodString: ZodString,
state: SchemaState,
): Schema => {
const zodStringChecks = getZodStringChecks(zodString);
const format = mapStringFormat(zodStringChecks);
const patterns = mapPatterns(zodStringChecks);
const minLength =
zodStringChecks.length?.[0]?.value ?? zodStringChecks.min?.[0]?.value;
const maxLength =
zodStringChecks.length?.[0]?.value ?? zodStringChecks.max?.[0]?.value;
const contentEncoding = satisfiesVersion(state.components.openapi, '3.1.0')
? mapContentEncoding(zodStringChecks)
: undefined;

if (patterns.length <= 1) {
return {
Expand All @@ -21,6 +28,7 @@ export const createStringSchema = (zodString: ZodString): Schema => {
...(patterns[0] && { pattern: patterns[0] }),
...(minLength !== undefined && { minLength }),
...(maxLength !== undefined && { maxLength }),
...(contentEncoding && { contentEncoding }),
},
};
}
Expand All @@ -35,6 +43,7 @@ export const createStringSchema = (zodString: ZodString): Schema => {
...(patterns[0] && { pattern: patterns[0] }),
...(minLength !== undefined && { minLength }),
...(maxLength !== undefined && { maxLength }),
...(contentEncoding && { contentEncoding }),
},
...patterns.slice(1).map(
(pattern): oas31.SchemaObject => ({
Expand Down Expand Up @@ -154,3 +163,13 @@ const mapStringFormat = (

return undefined;
};

const mapContentEncoding = (
zodStringChecks: ZodStringCheckMap,
): string | undefined => {
if (zodStringChecks.base64) {
return 'base64';
}

return undefined;
};

0 comments on commit 4219200

Please sign in to comment.