Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
49 changes: 49 additions & 0 deletions src/JsonSchemaGen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ const make = Effect.gen(function* () {
const refStore = new Map<string, JsonSchema.JsonSchema>()

function cleanupSchema(schema: JsonSchema.JsonSchema) {
// Handle boolean schemas (true/false)
if (typeof schema === "boolean") {
return schema
}

// Ensure schema is an object before using 'in' operator
if (typeof schema !== "object" || schema === null) {
return schema
}

if (
"type" in schema &&
Array.isArray(schema.type) &&
Expand Down Expand Up @@ -75,6 +85,17 @@ const make = Effect.gen(function* () {
asStruct = true,
) {
schema = cleanupSchema(schema)

// Early return for boolean schemas
if (typeof schema === "boolean") {
return
}

// Ensure schema is an object before property access
if (typeof schema !== "object" || schema === null) {
return
}

const enumSuffix = childName?.endsWith("Enum") ? "" : "Enum"
if ("$ref" in schema) {
if (seenRefs.has(schema.$ref)) {
Expand Down Expand Up @@ -210,6 +231,23 @@ const make = Effect.gen(function* () {
topLevel = false,
): Option.Option<string> => {
schema = cleanupSchema(schema)

// Handle boolean schemas
if (typeof schema === "boolean") {
if (schema === true) {
// true = any/unknown
return Option.some(transformer.onUnknown({ importName }))
} else {
// false = never/no additional items - return empty/none
return Option.none()
}
}

// Ensure schema is an object before property access
if (typeof schema !== "object" || schema === null) {
return Option.none()
}

if ("properties" in schema) {
const obj = schema as JsonSchema.Object
const required = obj.required ?? []
Expand Down Expand Up @@ -413,6 +451,9 @@ const make = Effect.gen(function* () {
return { $id: "/schemas/any" }
} else if (Array.isArray(schema)) {
return { anyOf: schema }
} else if (typeof schema === "boolean") {
// Handle boolean schemas: false means no additional items, true means any item
return schema === false ? { not: {} } : { $id: "/schemas/any" }
}
return schema
}
Expand Down Expand Up @@ -524,6 +565,8 @@ export class JsonSchemaTransformer extends Context.Tag("JsonSchemaTransformer")<
readonly source: string
}>
}): string

onUnknown(options: { readonly importName: string }): string
}
>() {}

Expand Down Expand Up @@ -655,6 +698,9 @@ export const layerTransformerSchema = Layer.sync(JsonSchemaTransformer, () => {
onUnion({ importName, items }) {
return `${importName}.Union(${items.map((_) => `${toComment(_.description)}${_.source}`).join(",\n")})`
},
onUnknown({ importName }) {
return `${importName}.Unknown`
},
})
})

Expand Down Expand Up @@ -716,6 +762,9 @@ export type ${name} = (typeof ${name})[keyof typeof ${name}];`
}
return `{\n ${items.map(({ description, title, source }) => `${toComment(description)}${JSON.stringify(Option.getOrNull(title))}: ${source}`).join(",\n ")}} as const\n`
},
onUnknown() {
return "unknown"
},
}),
)

Expand Down
120 changes: 120 additions & 0 deletions test-fixtures/boolean-schema-test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{
"openapi": "3.1.0",
"info": {
"title": "Boolean Schema Test API",
"version": "1.0.0",
"description": "Test cases for boolean schema support in OpenAPI 3.1"
},
"paths": {
"/test-tuple-false": {
"get": {
"operationId": "getTupleFalse",
"summary": "Test tuple with items: false (no additional items)",
"responses": {
"200": {
"description": "Strict tuple [integer, string]",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": false,
"prefixItems": [
{ "type": "integer" },
{ "type": "string" }
]
}
}
}
}
}
}
},
"/test-tuple-true": {
"get": {
"operationId": "getTupleTrue",
"summary": "Test tuple with items: true (any additional items)",
"responses": {
"200": {
"description": "Tuple with any additional items",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": true,
"prefixItems": [
{ "type": "integer" },
{ "type": "string" }
]
}
}
}
}
}
}
},
"/test-nested-tuple": {
"get": {
"operationId": "getNestedTuple",
"summary": "Test nested arrays with boolean schemas",
"responses": {
"200": {
"description": "Array of strict tuples",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "array",
"items": false,
"prefixItems": [
{ "type": "number", "description": "timestamp" },
{ "type": "number", "description": "value" }
]
}
}
}
}
}
}
}
},
"/test-mixed-schemas": {
"get": {
"operationId": "getMixedSchemas",
"summary": "Test response with both boolean and object schemas",
"responses": {
"200": {
"description": "Mixed schema types",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"strictTuple": {
"type": "array",
"items": false,
"prefixItems": [
{ "type": "string" }
]
},
"openTuple": {
"type": "array",
"items": true,
"prefixItems": [
{ "type": "string" }
]
},
"normalArray": {
"type": "array",
"items": { "type": "string" }
}
}
}
}
}
}
}
}
}
}
}
42 changes: 42 additions & 0 deletions test-fixtures/test-boolean-schemas.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash

# Test script for boolean schema support

echo "Testing Boolean Schema Support in OpenAPI 3.1"
echo "============================================="
echo ""

# Build the project
echo "Building project..."
npm run build:ts > /dev/null 2>&1

# Test 1: Boolean schema test fixture
echo "Test 1: Boolean schema test fixture"
node dist/main.js -s test-fixtures/boolean-schema-test.json -n BooleanSchemaTestClient > /tmp/boolean-test-output.ts 2>&1
if [ $? -eq 0 ]; then
echo "✅ SUCCESS: Generated $(wc -l < /tmp/boolean-test-output.ts) lines of code"
else
echo "❌ FAILED"
cat /tmp/boolean-test-output.ts | grep -A2 "ERROR"
exit 1
fi

echo ""

# Test 2: S2 OpenAPI spec (real-world case)
echo "Test 2: S2 OpenAPI spec (real-world case)"
if [ -f /tmp/s2-openapi.json ]; then
node dist/main.js -s /tmp/s2-openapi.json -n S2Client > /tmp/s2-test-output.ts 2>&1
if [ $? -eq 0 ]; then
echo "✅ SUCCESS: Generated $(wc -l < /tmp/s2-test-output.ts) lines of code"
else
echo "❌ FAILED"
cat /tmp/s2-test-output.ts | grep -A2 "ERROR"
exit 1
fi
else
echo "⚠️ SKIPPED: S2 spec not found at /tmp/s2-openapi.json"
fi

echo ""
echo "All tests passed! ✅"