Skip to content

Conversation

@ecito
Copy link
Contributor

@ecito ecito commented Nov 4, 2025

Description

This PR fixes two bugs in enum schema generation that caused incompatibility with Swift's Codable:

Bug 1: String-backed enums using case names instead of raw values

String-backed enums (enum Size: String { case small = "sm" }) were generating schemas with case names (["small"]) instead of raw values (["sm"]). This broke compatibility with Codable, which uses raw values for encoding/decoding.

Example:

enum Size: String {
  case small = "sm"
  case medium = "md"
}
  • Before: {"type": "string", "enum": ["small", "medium"]}
  • After: {"type": "string", "enum": ["sm", "md"]}

This ensures generated schemas validate JSON that Swift's standard JSONDecoder can decode.

Bug 2: Backticked identifiers including backticks in schemas

Backticked Swift identifiers (e.g., case \default`) were incorrectly including the backticks in JSON schemas, generating "`default`"instead of"default"`.

Example:

enum Keywords {
  case `default`
  case `public`
}
  • Before: {"type": "string", "enum": ["\default`", "`public`"]}` ❌
  • After: {"type": "string", "enum": ["default", "public"]}

Both fixes ensure the generated schemas match Swift's Codable behavior.

Changes Made

Core Implementation

  • SchemableEnumCase.swift: Added raw value extraction logic to capture String raw values from enum cases
  • SchemaGenerator.swift:
    • Added String.trimmingBackticks() extension to strip backticks from identifiers
    • Updated enum schema generation to use raw values when present
    • Updated switch case matching to use raw values instead of case names

Documentation

  • Macros.md: Enhanced enum documentation with separate sections for:
    • Simple enums (without raw values)
    • String-backed enums (with raw values)
    • Enums with associated values
    • Added clear examples showing the correct behavior and emphasizing Codable compatibility

Tests

  • PollExampleTests.swift: Updated test data to use correct raw values ("Parental Guidance Suggested 13+" instead of "pg13")
  • Regenerated snapshots: Updated schema snapshots to reflect correct enum values
  • BacktickEnumTests.swift: Added macro expansion tests for backticked enum cases
  • BacktickEnumIntegrationTests.swift: Added integration tests covering:
    • Schema generation for backticked enums (with and without raw values)
    • JSON parsing/validation with backticked enums
    • Verification that backticks are properly stripped

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Testing

All tests pass:

  • ✅ Macro expansion tests (BacktickEnumTests)
  • ✅ Integration tests (BacktickEnumIntegrationTests, PollExampleTests)
  • ✅ Existing enum tests continue to pass
  • ✅ Schema generation matches Codable behavior

Test Coverage

  • Enums without raw values continue to work correctly
  • String-backed enums now correctly use raw values
  • Mixed enums (some cases with custom raw values, some implicit) work correctly
  • Backticked identifiers are properly handled
  • JSON parsing/validation works with corrected schemas

Additional Notes

Impact

This is a correctness fix with no breaking changes to the API:

  • The macro syntax remains unchanged
  • Existing code continues to compile
  • Only the generated schemas change to be correct

However, if users were relying on the incorrect behavior (schemas with case names for String-backed enums), their schemas will change. This is the correct behavior, as the previous output was incompatible with Codable.

Related Files Changed

7 files changed, 278 insertions(+), 15 deletions(-)

Sources/JSONSchemaBuilder/Documentation.docc/Articles/Macros.md
Sources/JSONSchemaMacro/Schemable/SchemaGenerator.swift
Sources/JSONSchemaMacro/Schemable/SchemableEnumCase.swift
Tests/JSONSchemaIntegrationTests/BacktickEnumIntegrationTests.swift (new)
Tests/JSONSchemaIntegrationTests/PollExampleTests.swift
Tests/JSONSchemaIntegrationTests/__Snapshots__/PollExampleTests/defintion.1.json
Tests/JSONSchemaMacroTests/BacktickEnumTests.swift (new)

Verification Steps

To verify the fix works correctly:

  1. Raw values test:

    @Schemable
    enum Size: String {
      case small = "sm"
      case medium = "md"
    }
    
    // Schema now correctly has: "enum": ["sm", "md"]
  2. Backticks test:

    @Schemable
    enum Keywords {
      case `default`
      case `public`
    }
    
    // Schema now correctly has: "enum": ["default", "public"]
  3. Codable compatibility:

    let json = "{\"size\": \"sm\"}"
    let decoded = try JSONDecoder().decode(Product.self, from: json.data(using: .utf8)!)
    // Now works! Schema validates the same JSON that Codable accepts

ecito added 11 commits November 4, 2025 00:01
The @Schemable macro now respects Swift's CodingKeys enum when generating
JSON schemas. When a type defines a CodingKeys enum with custom string raw
values, those values are used as property names in the generated schema.

Key features:
- Automatic extraction of CodingKeys mapping from enum definitions
- Support for partial CodingKeys (missing entries fall back to property names)
- Proper priority order: @SchemaOptions(.key) > CodingKeys > keyStrategy > property name
- Works with both struct and class types
- Handles nested types with their own CodingKeys

Implementation:
- Added extractCodingKeys() method to parse CodingKeys enum via SwiftSyntax
- Updated schema generation to check CodingKeys mapping before other strategies
- Maintains backward compatibility (no breaking changes)
This fix ensures String-backed enums (enums conforming to RawRepresentable<String>)
generate schemas using their raw values instead of case names, making them compatible
with Swift's Codable behavior.

**Changes:**
- Updated SchemableEnumCase to extract and store raw values from enum cases
- Modified SchemaGenerator to use raw values in both enum value lists and switch statements
- Updated PollExampleTests to use raw values in test data
- Regenerated test snapshots to reflect correct schema output
- Enhanced documentation to clarify behavior for simple vs String-backed enums

**Example:**
```swift
enum Size: String {
  case small = "sm"
  case medium = "md"
}
```

Previously generated schema (incorrect):
```json
{"type": "string", "enum": ["small", "medium"]}
```

Now generates schema (correct):
```json
{"type": "string", "enum": ["sm", "md"]}
```

This ensures generated schemas validate JSON that Swift's Codable can decode.
Backticked Swift identifiers (e.g., case \`default\`) were incorrectly
including the backticks in generated JSON schemas, causing schemas to
use "\`default\`" instead of "default".

**Changes:**
- Added String.trimmingBackticks() extension to remove backticks
- Updated SchemableEnumCase to strip backticks from case names
- Updated SchemaGenerator to strip backticks when matching values
- Added comprehensive tests for backticked enum cases

**Test Coverage:**
- BacktickEnumTests: Macro expansion tests for backticked cases
- BacktickEnumIntegrationTests: Schema generation and parsing tests

This ensures schemas match Swift's Codable behavior, which also strips
backticks from identifiers during encoding/decoding.
@ecito ecito marked this pull request as ready for review November 5, 2025 13:52
@ajevans99 ajevans99 merged commit fe6572c into ajevans99:main Nov 5, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants