Skip to content

Commit 0295e21

Browse files
committed
feat: Allow normal format string behavior for cancelation mode Per review feedback on openjd-specifications PR #148, a format-string cancelation mode gets normal format string behavior: partial interpolation like "{{ 'NOTIFY' }}_THEN_TERMINATE" is valid, and the resolved value is checked against the two mode names at run time. Null semantics (dropping the cancelation object) now apply only when the mode is a whole-field "{{ ... }}" expression, per the Template Schemas 5.3 wording; a normal format string that resolves to the empty string is an invalid mode, matching the openjd-rs typed-resolution behavior.
1 parent 73e1370 commit 0295e21

2 files changed

Lines changed: 35 additions & 3 deletions

File tree

src/openjd/sessions/_runner_base.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,20 @@ def resolve_period(period: Any) -> Optional[int]:
151151
if cancelation is None:
152152
return (None, None)
153153
if isinstance(cancelation, CancelationMethodDeferred_2023_09):
154+
# Null semantics apply only to a whole-field expression
155+
# ("{{ ... }}" with no surrounding text, target type string? —
156+
# Template Schemas 5.3). A normal format string that happens to
157+
# resolve to the empty string is NOT null; it falls through to the
158+
# "must resolve to..." error below.
159+
raw = str(cancelation.mode).strip()
160+
is_whole_field = (
161+
raw.startswith("{{")
162+
and raw.endswith("}}")
163+
and raw.count("{{") == 1
164+
and raw.count("}}") == 1
165+
)
154166
mode = cancelation.mode.resolve(symtab=symtab)
155-
if mode == "":
167+
if mode == "" and is_whole_field:
156168
# Null mode drops the ENTIRE cancelation object: mode is the
157169
# object's required discriminator, so an "omitted" mode cannot
158170
# leave a partial object behind. The action behaves exactly as

test/openjd/sessions_v0/test_wrap_cancelation.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,8 @@ def test_no_cancelation_renders_null_mode_as_empty(
258258
class TestResolveEffectiveCancelation:
259259
"""Unit tests for the shared deferred-cancelation resolution helper.
260260
261-
A CancelationMethodDeferred carries a whole-field format-string mode
262-
whose TERMINATE-vs-NOTIFY_THEN_TERMINATE decision is made at run time
261+
A CancelationMethodDeferred carries a format-string mode whose
262+
TERMINATE-vs-NOTIFY_THEN_TERMINATE decision is made at run time
263263
against the live symbol table (see resolve_effective_cancelation's
264264
docstring for the full story).
265265
"""
@@ -319,6 +319,26 @@ def test_mode_resolving_garbage_raises(self) -> None:
319319
with pytest.raises(ValueError, match="must resolve to"):
320320
resolve_effective_cancelation(cancelation, self._symtab(X="SOMETHING_ELSE"))
321321

322+
def test_partial_interpolation_mode_resolves_normally(self) -> None:
323+
# Normal format string behavior (Template Schemas 5.3): partial
324+
# interpolation is permitted; the resolved value is checked
325+
# against the two mode names.
326+
from openjd.sessions._runner_base import resolve_effective_cancelation
327+
328+
cancelation = self._deferred("{{X}}_THEN_TERMINATE", "{{P}}")
329+
result = resolve_effective_cancelation(cancelation, self._symtab(X="NOTIFY", P=45))
330+
assert result == ("NOTIFY_THEN_TERMINATE", 45)
331+
332+
def test_partial_interpolation_mode_resolving_empty_raises(self) -> None:
333+
# Null semantics (dropping the cancelation object) apply only to a
334+
# whole-field expression. A normal format string that resolves to
335+
# the empty string is not null — it is an invalid mode.
336+
from openjd.sessions._runner_base import resolve_effective_cancelation
337+
338+
cancelation = self._deferred("{{X}}{{Y}}")
339+
with pytest.raises(ValueError, match="must resolve to"):
340+
resolve_effective_cancelation(cancelation, self._symtab(X=None, Y=None))
341+
322342
def test_resolved_period_exceeding_cap_raises(self) -> None:
323343
# The static validator caps literal periods at 600 (Template
324344
# Schemas 5.3.2); format-string values could not be checked at

0 commit comments

Comments
 (0)