Skip to content

Conversation

@mwhansen
Copy link
Contributor

@mwhansen mwhansen commented Sep 20, 2025

Description

Fixes nullable metadata loss for OpenAPI 3.1 choice fields when using ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE=False.

Problem

When using OAS_VERSION="3.1.0" with ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE=False, nullable choice fields were losing their nullable: true metadata during enum postprocessing, causing client generation issues.

Affected Configuration

SPECTACULAR_SETTINGS = {
    "OAS_VERSION": "3.1.0",
    "ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE": False,
}

Symptoms

Nullable choice fields defined like:

pet_type = serializers.ChoiceField(allow_null=True, choices=PET_TYPE_CHOICES)

Were generating schemas without nullable information:

# Before (Broken)
petType:
  $ref: '#/components/schemas/PetTypeEnum'  # Missing nullable!

# After (Fixed)  
petType:
  allOf:
    - $ref: '#/components/schemas/PetTypeEnum'
  nullable: true

Root Cause

  1. OpenAPI 3.1 converts nullable enums to type=['string', 'null']
  2. Enum postprocessing removes 'null' from type array (line 160 in hooks.py)
  3. When ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE=False, no separate NullEnum component is created
  4. Result: nullable information lost entirely

Files Changed

  • drf_spectacular/hooks.py: Core fix for nullable preservation
  • tests/test_regressions.py: Regression test to prevent future issues

@mwhansen mwhansen force-pushed the fix/openapi31-nullable-choice-fields-regression branch from 942421b to e1d86c6 Compare September 20, 2025 10:12
@codecov
Copy link

codecov bot commented Sep 20, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.50%. Comparing base (39cb3d5) to head (f428d95).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1454   +/-   ##
=======================================
  Coverage   98.49%   98.50%           
=======================================
  Files          76       76           
  Lines        9266     9286   +20     
=======================================
+ Hits         9127     9147   +20     
  Misses        139      139           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…D_EXPLICIT_BLANK_NULL_CHOICE=False

When using OpenAPI 3.1 with ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE=False, nullable
choice fields were losing their nullable metadata during enum postprocessing.

The issue occurred when:
1. OpenAPI 3.1 converts nullable enums to type=['string', 'null']
2. Enum postprocessing removes 'null' from type array (line 160 in hooks.py)
3. No separate NullEnum component is created (due to setting=False)
4. Result: nullable information is lost entirely

The fix detects when 'null' is removed from a type array and preserves the
nullable metadata when no separate component will be created.

Added comprehensive regression test to prevent future issues.

Fixes regression introduced in v0.28.0 with specific OpenAPI 3.1 + no NullEnum settings.
@mwhansen mwhansen force-pushed the fix/openapi31-nullable-choice-fields-regression branch from e1d86c6 to bd68603 Compare September 20, 2025 10:32
@tfranzel
Copy link
Owner

tfranzel commented Nov 1, 2025

Hey @mwhansen,

Thanks for the PR. I finally had some time to review this. Unfortunately, I think we cannot do this.

nullable: true is not a thing anymore in OAS >= 3.1. Even if the schema validation itself does not fail, that statement is basically inert and is not supposed to have any effect anymore. So we kind of lost the ability to specify the null outside $ref with that change.

Furthermore, turning off ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE while having components (i.e. using the enum postproc hook) is a tradeoff. You will loose the null information for a simpler schema construction. There simply no sane way to reuse the same enum component in different serializers with different parametrisations (blank=True/False, null=True/False).

The only way to resolve this (without Null/BlankEnum) would be creating multiple versions of the same enum component with and without the blank/null, but that is even less desirable imho.

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