Skip to content

Commit af099f3

Browse files
authored
Merge pull request #3396 from hey-api/refactor/valibot-plugin-nullish
refactor: valibot plugin nullish
2 parents a55a5e1 + ea6f386 commit af099f3

42 files changed

Lines changed: 1288 additions & 674 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/brown-ideas-matter.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hey-api/shared": patch
3+
---
4+
5+
**internal**: export schema walker interfaces

.changeset/clean-singers-stand.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@hey-api/openapi-ts": minor
3+
---
4+
5+
**plugin(valibot)**: remove `enum.nodes.nullable` resolver node
6+
7+
### Removed resolver node
8+
9+
Valibot plugin no longer exposes the `enum.nodes.nullable` node. It was refactored so that nullable values are handled outside of resolvers.

.changeset/stale-falcons-raise.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hey-api/openapi-ts": patch
3+
---
4+
5+
**plugin(valibot)**: use `.nullable()` and `.nullish()` methods

docs/openapi-ts/migrating.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ description: Migrating to @hey-api/openapi-ts.
77

88
While we try to avoid breaking changes, sometimes it's unavoidable in order to offer you the latest features. This page lists changes that require updates to your code. If you run into a problem with migration, please [open an issue](https://github.com/hey-api/openapi-ts/issues).
99

10+
## v0.93.0
11+
12+
### Removed resolver node
13+
14+
Valibot and Zod plugins no longer expose the `enum.nodes.nullable` node. Both plugins were refactored so that nullable values are handled outside of resolvers.
15+
1016
## v0.92.0
1117

1218
### Updated Symbol interface

packages/openapi-python/src/plugins/pydantic/shared/export.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
1-
import type { Symbol } from '@hey-api/codegen-core';
2-
import type { IR } from '@hey-api/shared';
1+
import { applyNaming, pathToName } from '@hey-api/shared';
32

43
// import { createSchemaComment } from '../../../plugins/shared/utils/schema';
54
import { $ } from '../../../py-dsl';
5+
import type { ProcessorContext } from './processor';
66
// import { identifiers } from '../v2/constants';
77
// import { pipesToNode } from './pipes';
88
import type { Ast, IrSchemaToAstOptions } from './types';
99

1010
export function exportAst({
11-
// ast,
11+
meta,
12+
naming,
13+
namingAnchor,
14+
path,
1215
plugin,
13-
// schema,
14-
// state,
15-
symbol,
16-
}: IrSchemaToAstOptions & {
17-
ast: Ast;
18-
schema: IR.SchemaObject;
19-
symbol: Symbol;
20-
}): void {
21-
// const v = plugin.external('valibot.v');
22-
const classDef = $.class(symbol);
16+
tags,
17+
}: Pick<IrSchemaToAstOptions, 'state'> &
18+
ProcessorContext & {
19+
ast: Ast;
20+
}): void {
21+
const name = pathToName(path, { anchor: namingAnchor });
22+
const symbol = plugin.symbol(applyNaming(name, naming), {
23+
meta: {
24+
category: 'schema',
25+
path,
26+
tags,
27+
tool: 'pydantic',
28+
...meta,
29+
},
30+
});
31+
32+
const baseModel = plugin.external('pydantic.BaseModel');
33+
const classDef = $.class(symbol).extends(baseModel);
2334
// .export()
2435
// .$if(plugin.config.comments && createSchemaComment(schema), (c, v) => c.doc(v))
2536
// .$if(state.hasLazyExpression['~ref'], (c) =>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type {
2+
IR,
3+
NamingConfig,
4+
SchemaProcessorContext,
5+
SchemaProcessorResult,
6+
} from '@hey-api/shared';
7+
8+
import type { IrSchemaToAstOptions } from './types';
9+
10+
export type ProcessorContext = Pick<IrSchemaToAstOptions, 'plugin'> &
11+
SchemaProcessorContext & {
12+
naming: NamingConfig;
13+
schema: IR.SchemaObject;
14+
};
15+
16+
export type ProcessorResult = SchemaProcessorResult<ProcessorContext>;

packages/openapi-python/src/plugins/pydantic/shared/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { IR, SchemaExtractor } from '@hey-api/shared';
33

44
import type { $ } from '../../../py-dsl';
55
import type { PydanticPlugin } from '../types';
6+
import type { ProcessorContext } from './processor';
67

78
export type Ast = {
89
/**
@@ -32,7 +33,7 @@ export type IrSchemaToAstOptions = {
3233
/** The plugin instance. */
3334
plugin: PydanticPlugin['Instance'];
3435
/** Optional schema extractor function. */
35-
schemaExtractor?: SchemaExtractor;
36+
schemaExtractor?: SchemaExtractor<ProcessorContext>;
3637
/** The plugin state references. */
3738
state: Refs<PluginState>;
3839
};

packages/openapi-python/src/plugins/pydantic/v2/plugin.ts

Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import type { SymbolMeta } from '@hey-api/codegen-core';
2-
import { fromRef, ref, refs } from '@hey-api/codegen-core';
2+
import { fromRef, ref } from '@hey-api/codegen-core';
33
import type { IR, SchemaWithType } from '@hey-api/shared';
4-
import { applyNaming, deduplicateSchema, pathToJsonPointer, refToName } from '@hey-api/shared';
4+
import { deduplicateSchema, pathToJsonPointer } from '@hey-api/shared';
55

66
// import { $ } from '../../../py-dsl';
7-
import { exportAst } from '../shared/export';
8-
import type { Ast, IrSchemaToAstOptions, PluginState } from '../shared/types';
7+
import type { Ast, IrSchemaToAstOptions } from '../shared/types';
98
import type { PydanticPlugin } from '../types';
9+
import { createProcessor } from './processor';
1010
import { irSchemaWithTypeToAst } from './toAst';
1111

1212
export function irSchemaToAst({
@@ -25,7 +25,9 @@ export function irSchemaToAst({
2525
resource: 'definition',
2626
resourceId: pathToJsonPointer(fromRef(state.path)),
2727
},
28+
naming: plugin.config.definitions,
2829
path: fromRef(state.path),
30+
plugin,
2931
schema,
3032
});
3133
if (extracted !== schema) schema = extracted;
@@ -119,39 +121,6 @@ export function irSchemaToAst({
119121
};
120122
}
121123

122-
function handleComponent({
123-
plugin,
124-
schema,
125-
state,
126-
}: IrSchemaToAstOptions & {
127-
schema: IR.SchemaObject;
128-
}): void {
129-
const $ref = pathToJsonPointer(fromRef(state.path));
130-
const baseName = refToName($ref);
131-
const symbol = plugin.symbol(applyNaming(baseName, plugin.config.definitions), {
132-
meta: {
133-
category: 'schema',
134-
path: fromRef(state.path),
135-
resource: 'definition',
136-
resourceId: $ref,
137-
tags: fromRef(state.tags),
138-
tool: 'pydantic',
139-
},
140-
});
141-
const ast = irSchemaToAst({
142-
plugin,
143-
schema,
144-
state,
145-
});
146-
exportAst({
147-
ast,
148-
plugin,
149-
schema,
150-
state,
151-
symbol,
152-
});
153-
}
154-
155124
export const handlerV2: PydanticPlugin['Handler'] = ({ plugin }) => {
156125
plugin.symbol('Any', {
157126
external: 'typing',
@@ -202,36 +171,47 @@ export const handlerV2: PydanticPlugin['Handler'] = ({ plugin }) => {
202171
},
203172
});
204173

205-
plugin.forEach('operation', 'parameter', 'requestBody', 'schema', 'webhook', (event) => {
206-
const state = refs<PluginState>({
207-
hasLazyExpression: false,
208-
path: event._path,
209-
tags: event.tags,
210-
});
174+
const processor = createProcessor(plugin);
211175

176+
plugin.forEach('operation', 'parameter', 'requestBody', 'schema', 'webhook', (event) => {
212177
switch (event.type) {
213178
case 'parameter':
214-
handleComponent({
215-
// baseName: event.name,
179+
processor.process({
180+
meta: {
181+
resource: 'definition',
182+
resourceId: pathToJsonPointer(event._path),
183+
},
184+
naming: plugin.config.definitions,
185+
path: event._path,
216186
plugin,
217187
schema: event.parameter.schema,
218-
state,
188+
tags: event.tags,
219189
});
220190
break;
221191
case 'requestBody':
222-
handleComponent({
223-
// baseName: event.name,
192+
processor.process({
193+
meta: {
194+
resource: 'definition',
195+
resourceId: pathToJsonPointer(event._path),
196+
},
197+
naming: plugin.config.definitions,
198+
path: event._path,
224199
plugin,
225200
schema: event.requestBody.schema,
226-
state,
201+
tags: event.tags,
227202
});
228203
break;
229204
case 'schema':
230-
handleComponent({
231-
// baseName: event.name,
205+
processor.process({
206+
meta: {
207+
resource: 'definition',
208+
resourceId: pathToJsonPointer(event._path),
209+
},
210+
naming: plugin.config.definitions,
211+
path: event._path,
232212
plugin,
233213
schema: event.schema,
234-
state,
214+
tags: event.tags,
235215
});
236216
break;
237217
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { refs } from '@hey-api/codegen-core';
2+
import type { IR } from '@hey-api/shared';
3+
import { createSchemaProcessor, pathToJsonPointer } from '@hey-api/shared';
4+
5+
import { exportAst } from '../shared/export';
6+
import type { ProcessorContext, ProcessorResult } from '../shared/processor';
7+
import type { PluginState } from '../shared/types';
8+
import type { PydanticPlugin } from '../types';
9+
import { irSchemaToAst } from './plugin';
10+
11+
export function createProcessor(plugin: PydanticPlugin['Instance']): ProcessorResult {
12+
const processor = createSchemaProcessor();
13+
14+
const hooks = [plugin.config['~hooks']?.schemas, plugin.context.config.parser.hooks.schemas];
15+
16+
function extractor(ctx: ProcessorContext): IR.SchemaObject {
17+
if (processor.hasEmitted(ctx.path)) {
18+
return ctx.schema;
19+
}
20+
21+
for (const hook of hooks) {
22+
const result = hook?.shouldExtract?.(ctx);
23+
if (result) {
24+
process({
25+
namingAnchor: processor.context.anchor,
26+
tags: processor.context.tags,
27+
...ctx,
28+
});
29+
return { $ref: pathToJsonPointer(ctx.path) };
30+
}
31+
}
32+
33+
return ctx.schema;
34+
}
35+
36+
function process(ctx: ProcessorContext): void {
37+
if (!processor.markEmitted(ctx.path)) return;
38+
39+
processor.withContext({ anchor: ctx.namingAnchor, tags: ctx.tags }, () => {
40+
const state = refs<PluginState>({
41+
hasLazyExpression: false,
42+
path: ctx.path,
43+
tags: ctx.tags,
44+
});
45+
46+
const ast = irSchemaToAst({
47+
plugin,
48+
schema: ctx.schema,
49+
schemaExtractor: extractor,
50+
state,
51+
});
52+
53+
exportAst({ ...ctx, ast, plugin, state });
54+
});
55+
}
56+
57+
return { process };
58+
}

packages/openapi-ts-tests/main/test/__snapshots__/2.0.x/plugins/valibot/default/valibot.gen.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,8 @@ export const vModelWithStringError = v.object({
197197
* This is a model with one string property
198198
*/
199199
export const vModelWithNullableString = v.object({
200-
nullableProp: v.optional(v.union([v.string(), v.null()])),
201-
nullableRequiredProp: v.union([v.string(), v.null()])
200+
nullableProp: v.nullish(v.string()),
201+
nullableRequiredProp: v.nullable(v.string())
202202
});
203203

204204
/**
@@ -581,8 +581,8 @@ export const vCallToTestOrderOfParamsData = v.object({
581581
parameterStringWithDefault: v.optional(v.string(), 'Hello World!'),
582582
parameterStringWithEmptyDefault: v.optional(v.string(), ''),
583583
parameterStringWithNoDefault: v.string(),
584-
parameterStringNullableWithNoDefault: v.optional(v.union([v.string(), v.null()])),
585-
parameterStringNullableWithDefault: v.optional(v.union([v.string(), v.null()]), null)
584+
parameterStringNullableWithNoDefault: v.nullish(v.string()),
585+
parameterStringNullableWithDefault: v.nullish(v.string(), null)
586586
})
587587
});
588588

0 commit comments

Comments
 (0)