From 9ec4ed825d09570c6685c4704b9376a05a48cf02 Mon Sep 17 00:00:00 2001 From: afsuyadi Date: Wed, 15 Jul 2026 12:03:46 +0700 Subject: [PATCH 1/5] feat(api): add postprocessing hook to restrict max length --- api/api/openapi.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/api/api/openapi.py b/api/api/openapi.py index d01a2ffdff54..687e29023f77 100644 --- a/api/api/openapi.py +++ b/api/api/openapi.py @@ -12,6 +12,11 @@ from rest_framework.request import Request from typing_extensions import is_typeddict +from environments.identities.traits.constants import ( + TRAIT_STRING_VALUE_MAX_LENGTH, +) +from oauth2_metadata.dataclasses import OAuthConfig + def append_meta(schema: dict[str, Any], meta: dict[str, Any]) -> dict[str, Any]: """ @@ -310,3 +315,26 @@ def postprocessing_assign_tags( result["tags"] = TAGS return result + + +def postprocessing_add_trait_value_max_length( + result: dict[str, Any], generator: Any, **kwargs: Any +) -> dict[str, Any]: + """Document the `trait_value` string length limit enforced by the API. + + `trait_value` schemas are generated from `flagsmith_schemas`, which + types the field as `flag_engine.segments.types.ContextValue` — a plain + `str | int | float | bool | None` union with no length constraint. The + API enforces `TRAIT_STRING_VALUE_MAX_LENGTH` at runtime (see + `environments.identities.traits.fields.TraitValueField`), so patch the + generated schema here to keep the two in sync. + """ + for schema in result.get("components", {}).get("schemas", {}).values(): + trait_value_schema = schema.get("properties", {}).get("trait_value") + if not isinstance(trait_value_schema, dict): + continue + for variant in trait_value_schema.get("anyOf", []): + if variant.get("type") == "string": + variant["maxLength"] = TRAIT_STRING_VALUE_MAX_LENGTH + + return result From cdcc99f75a0170fac4f28352180262cc69750292 Mon Sep 17 00:00:00 2001 From: afsuyadi Date: Wed, 15 Jul 2026 12:04:23 +0700 Subject: [PATCH 2/5] feat(api): call postprocessing hook method --- api/app/settings/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/api/app/settings/common.py b/api/app/settings/common.py index f2768dfc454b..c287b8aa5319 100644 --- a/api/app/settings/common.py +++ b/api/app/settings/common.py @@ -588,6 +588,7 @@ "POSTPROCESSING_HOOKS": [ "drf_spectacular.hooks.postprocess_schema_enums", "api.openapi.postprocessing_assign_tags", + "api.openapi.postprocessing_add_trait_value_max_length", ], "ENUM_NAME_OVERRIDES": { # Overrides to use specific schema names for fields named "type". From ce3602966ae1f4fd8e04aac2aa954efbf87a6cf5 Mon Sep 17 00:00:00 2001 From: afsuyadi Date: Wed, 15 Jul 2026 12:05:22 +0700 Subject: [PATCH 3/5] feat(tests): add unit tests for testing string's max length --- api/tests/unit/api/test_unit_openapi.py | 82 +++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/api/tests/unit/api/test_unit_openapi.py b/api/tests/unit/api/test_unit_openapi.py index 86076be7b280..05d2fdc3a798 100644 --- a/api/tests/unit/api/test_unit_openapi.py +++ b/api/tests/unit/api/test_unit_openapi.py @@ -8,9 +8,11 @@ from api.openapi import ( TAGS, TypedDictSchemaExtension, + postprocessing_add_trait_value_max_length, postprocessing_assign_tags, preprocessing_filter_spec, ) +from environments.identities.traits.constants import TRAIT_STRING_VALUE_MAX_LENGTH def test_typeddict_schema_extension__nested_typed_dict__renders_expected_schema() -> ( @@ -266,3 +268,83 @@ class MyModel(TypedDict): # Then assert name == "MyModel" + + +def test_postprocessing_add_trait_value_max_length__string_variant__adds_max_length() -> ( + None +): + # Given + result: dict[str, Any] = { + "components": { + "schemas": { + "TraitInput": { + "properties": { + "trait_value": { + "anyOf": [ + {"type": "integer"}, + {"type": "number"}, + {"type": "boolean"}, + {"type": "string"}, + {"type": "null"}, + ], + }, + }, + }, + }, + }, + } + + # When + postprocessing_add_trait_value_max_length(result, generator=None) + + # Then + trait_value_schema = result["components"]["schemas"]["TraitInput"]["properties"][ + "trait_value" + ] + assert trait_value_schema["anyOf"] == [ + {"type": "integer"}, + {"type": "number"}, + {"type": "boolean"}, + {"type": "string", "maxLength": TRAIT_STRING_VALUE_MAX_LENGTH}, + {"type": "null"}, + ] + + +def test_postprocessing_add_trait_value_max_length__no_trait_value_property__left_unchanged() -> ( + None +): + # Given + result: dict[str, Any] = { + "components": { + "schemas": { + "Organisation": { + "properties": { + "name": {"type": "string"}, + }, + }, + }, + }, + } + + # When + postprocessing_add_trait_value_max_length(result, generator=None) + + # Then + assert result["components"]["schemas"]["Organisation"] == { + "properties": { + "name": {"type": "string"}, + }, + } + + +def test_postprocessing_add_trait_value_max_length__no_components__returns_result_unchanged() -> ( + None +): + # Given + result: dict[str, Any] = {"paths": {}} + + # When + output = postprocessing_add_trait_value_max_length(result, generator=None) + + # Then + assert output == {"paths": {}} From a030dab2b8b2774f1b370db3bee775f334665f2a Mon Sep 17 00:00:00 2001 From: afsuyadi Date: Wed, 15 Jul 2026 12:05:48 +0700 Subject: [PATCH 4/5] feat(sdk): define maxLength --- sdk/openapi.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk/openapi.yaml b/sdk/openapi.yaml index 2422d7dc3133..82371a88554b 100644 --- a/sdk/openapi.yaml +++ b/sdk/openapi.yaml @@ -449,6 +449,7 @@ components: - type: number - type: boolean - type: string + maxLength: 2000 - type: 'null' title: Trait Value transient: @@ -517,6 +518,7 @@ components: - type: number - type: boolean - type: string + maxLength: 2000 - type: 'null' title: Trait Value required: From 231c559f363ca58d73abd949e4ea0a73346f2c90 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 15 Jul 2026 06:14:24 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- api/api/openapi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/api/api/openapi.py b/api/api/openapi.py index 687e29023f77..5dd3849d6871 100644 --- a/api/api/openapi.py +++ b/api/api/openapi.py @@ -15,7 +15,6 @@ from environments.identities.traits.constants import ( TRAIT_STRING_VALUE_MAX_LENGTH, ) -from oauth2_metadata.dataclasses import OAuthConfig def append_meta(schema: dict[str, Any], meta: dict[str, Any]) -> dict[str, Any]: