Skip to content

A boolean flag satisfies an Integer request: bool is a subclass of int #619

Description

@aepfli

Summary

A boolean flag satisfies an Integer request. Evaluating a boolean flag through get_integer_details returns True with reason STATIC and no error code, where the specification requires the code default and TYPE_MISMATCH.

The application sees a plausible value — True behaves as 1 in arithmetic — and no indication that anything went wrong. That is the worst failure mode a feature flag has.

Cause

_typecheck_flag_value in openfeature/client.py:

type_map: TypeMap = {
    FlagType.BOOLEAN: bool,
    FlagType.STRING: str,
    FlagType.OBJECT: (dict, list),
    FlagType.FLOAT: float,
    FlagType.INTEGER: int,
}
py_type = type_map.get(flag_type)
...
if not isinstance(value, py_type):
    return TypeMismatchError(f"Expected type {py_type} but got {type(value)}")

bool is a subclass of int in Python, so isinstance(True, int) is True and the check passes:

>>> isinstance(True, int)
True

The reverse direction is fine — isinstance(1, float) is False, so an integer requested as a float is correctly a mismatch — which is why this is easy to miss.

Reproducing

from openfeature import api
from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider

api.set_provider(InMemoryProvider({
    "boolean-flag": InMemoryFlag(default_variant="on", variants={"on": True, "off": False}),
}))

details = api.get_client().get_integer_details("boolean-flag", 1)
print(details.value, details.reason, details.error_code)
# True Reason.STATIC None
# expected: 1 Reason.ERROR ErrorCode.TYPE_MISMATCH

Suggested fix

Reject bool explicitly when an integer or float was requested:

if flag_type in (FlagType.INTEGER, FlagType.FLOAT) and isinstance(value, bool):
    return TypeMismatchError(f"Expected type {py_type} but got {type(value)}")

Worth checking the same place for FlagType.OBJECT: (dict, list) accepts a list, which is correct per the spec's structure type, but is worth a test either way.

How it surfaced

Building the Python implementation of the cross-language provider conformance suite (spec#417). The suite runs an identical type-mismatch matrix against every provider in every language; this row passes everywhere else and fails only in Python, because only Python has bool as a subclass of int.

That is a reasonable advertisement for having more than one language implementation: a Java or Go suite could never have caught this.

The suite currently marks the case xfail(strict=True) with a pointer to this issue, so it stays visible and un-hides itself automatically once this is fixed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions