TR-7946 Generate nullable union types instead of aborting - #136
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
@mSprunskas , please review. Thank you. |
`X | nil` says a property is present but may hold no value. That is nullability, not a choice between two shapes, and every generated getter already returns null when the key is absent, so the union collapses to X with nothing lost. Applies to property types and to array item types, and works for named types as well as primitives. A union of two value types such as `string | boolean` is left alone, because it has no single representation to generate, so it still fails loudly. Covered by the nullable-union fixture in the Javascript and PHP REST client suites, and by a test asserting that a union of two value types is still refused.
20c1bba to
b995c89
Compare
|
Added test coverage before review, so this does not need the same round as #135. How to run the suite. PHPUnit cannot start on a stock Ubuntu 24.04 PHP 8.3, because docker run --rm -v "$PWD":/app -w /app -u $(id -u):$(id -g) \
gitlab.paysera.net:5050/paysera/developer-environment/mokejimai:dev php bin/phpunitThat image is PHP 7.4.3, matching What is covered. A Plus The tests bite. Against and green with the change. Version number. Bumped to |
TR-7946
Follow-up to #135. That PR rescues two type categories the generator was discarding; this one removes the last type-resolution blocker, nullable unions.
Kept separate deliberately: #135 is a pair of narrow bug fixes, whereas this changes how a RAML construct is interpreted and deserves its own decision. Neither depends on the other, but
app-evpbank/public-transfersneeds both.The problem
DefinitionValidator.php:83refuses unions outright:In practice you rarely see that message — a property typed
integer | nilreachesPropertyDefinitionBuilderas the raw string"integer | nil", is not recognised as a simple type, becomes aTYPE_REFERENCEto a type of that name, and fails later as:This was reported in INV-257 (Apr 2024) and closed "Won't Do" in Feb 2025, so the behaviour is long-standing and known.
Why it is worth fixing rather than declining again
X | nilis not really a union. It says a property is present but may hold no value — that is nullability, and RAML has no other way to express it. A union of two genuinely different shapes (string | boolean) is a different thing.Counting every union in
paysera/api-spec:string | nilinteger | nilTransferTimelineInspection | nilTimelineMessageCode | nildatetime | nilboolean | nilstring | boolean21 of 22 are nullability. Declining the whole feature to avoid the one hard case costs the other twenty-one — and those twenty-one are why
public-transferscannot be generated.What this PR does
X | nilcollapses toX. Nothing is lost: every generated getter isreturn this.get('key'), which already yieldsnullwhen the key is absent or null. Applies to property types and to array item types, and works for named types (TimelineMessageCode | nil) as well as primitives.A union of two real types is left exactly as it was, so
string | booleanstill fails loudly rather than silently picking a side. That case has no single representation to generate and should stay a spec-authoring decision.The change is confined to
PropertyDefinitionBuilder, at the one point that already normalises a property's declared type.Test plan
Regression — 27 fixture APIs, byte-identical output
Generated a JS client from every
api.ramlundertests/on unmodifiedmaster, then again with this change:No fixture currently uses a union, so this confirms the normalisation does not disturb any existing path.
Forward — with #135, all type resolution now completes
Against the real
app-evpbank/public-transfers/api.raml, with both branches applied andpaysera/api-spec!1789(the missing example files):The file ../examples/transfer_create_payza.json does not exist or is unreadableDid not found defined type "AccountingMetadata"Did not found defined type "string | nil"Two environment gaps on my side, both of which CI covers:
mbstring, which PHPUnit requires, and the only other runtimes on hand are PHP 7.4 containers that cannot run this codebase. The 27-API comparison above is my substitute — please treat the unit suite as a required check.public-transfersstill does not finish for me, but no longer for any type reason: it now reaches template rendering and fails inPayseraWordNetBundlewithcould not find driver. My PHP reportsPDO::getAvailableDrivers()→ none, so the WordNet SQLite lookup used for verb detection in resource paths cannot open. That is my environment, not this change — the 27 fixture APIs generate fine because their paths do not trigger that lookup. Anyone withpdo_sqliteshould be able to complete the generation and confirm the resulting client.No fixture covers a union, so the new path has no golden-master coverage. I would rather add one than not — happy to, if you tell me which shape you want pinned.
Context
@paysera/public-transfers-clientis stuck at 4.13.0 and missing fields that shipped long ago (vop_check.idfrom CORE-5433,vop_check.error_codefrom TR-7778), because it has not been regenerable for a long time. Consumers read raw payload keys instead. This PR plus #135 plus api-spec!1789 are what change that.Worth flagging: PLT-1813 records an intent to replace this generator with OpenAPI Generator. If that is close, you may prefer to take #135 alone (two narrow fixes) and leave unions unsupported. This PR is written so that decision stays open.