@mandujs/core 0.35.0 → 0.35.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/core",
3
- "version": "0.35.0",
3
+ "version": "0.35.1",
4
4
  "description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -140,11 +140,14 @@ export interface OpenAPIDocument {
140
140
  export function zodToOpenAPISchema(zodSchema: z.ZodTypeAny): OpenAPISchema {
141
141
  const typeName = getZodTypeName(zodSchema);
142
142
 
143
- // Handle ZodOptional
143
+ // Handle ZodOptional — optionality is expressed by the parent object's
144
+ // `required[]` array (or by `parameter.required: false`), NOT by the
145
+ // field's own schema. Emitting `nullable: true` here would conflate
146
+ // "may be absent" with "may literally be null", which breaks Postman,
147
+ // codegen, and Swagger UI (they'd all treat the field as nullable).
144
148
  if (typeName === "ZodOptional") {
145
149
  const inner = getZodInnerType(zodSchema);
146
- const innerSchema = inner ? zodToOpenAPISchema(inner) : {};
147
- return { ...innerSchema, nullable: true };
150
+ return inner ? zodToOpenAPISchema(inner) : {};
148
151
  }
149
152
 
150
153
  // Handle ZodDefault
@@ -147,12 +147,14 @@ describe("zodToOpenAPISchema", () => {
147
147
  });
148
148
 
149
149
  describe("modifiers", () => {
150
- test("should convert ZodOptional", () => {
150
+ test("should convert ZodOptional — unwraps without adding nullable (optional != nullable)", () => {
151
151
  const schema = z.string().optional();
152
152
  const result = zodToOpenAPISchema(schema);
153
153
 
154
154
  expect(result.type).toBe("string");
155
- expect(result.nullable).toBe(true);
155
+ // Optionality is expressed by the parent object's required[] / parameter.required,
156
+ // NOT by marking the field nullable. See generator.ts comment.
157
+ expect(result.nullable).toBeUndefined();
156
158
  });
157
159
 
158
160
  test("should convert ZodNullable", () => {
@@ -163,6 +165,26 @@ describe("zodToOpenAPISchema", () => {
163
165
  expect(result.nullable).toBe(true);
164
166
  });
165
167
 
168
+ test("should convert ZodOptional(ZodNullable) — inner nullable preserved", () => {
169
+ const schema = z.string().nullable().optional();
170
+ const result = zodToOpenAPISchema(schema);
171
+
172
+ expect(result.type).toBe("string");
173
+ expect(result.nullable).toBe(true);
174
+ });
175
+
176
+ test("object with optional field — field omitted from required[] but schema itself is not nullable", () => {
177
+ const schema = z.object({
178
+ name: z.string(),
179
+ age: z.number().optional(),
180
+ });
181
+ const result = zodToOpenAPISchema(schema);
182
+
183
+ expect(result.type).toBe("object");
184
+ expect(result.required).toEqual(["name"]);
185
+ expect(result.properties?.age).toEqual({ type: "number" });
186
+ });
187
+
166
188
  test("should convert ZodDefault", () => {
167
189
  const schema = z.number().default(10);
168
190
  const result = zodToOpenAPISchema(schema);