bug: `{{ var }}` with inner whitespace is neither substituted nor reported as unresolved
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 80/100
Research direction
Start in git_api/cli.py by comparing _interpolate() with _unresolved_placeholders(); the issue identifies their differing placeholder grammars. Check the existing tests for interpolation and unresolved-variable warnings, then add coverage for the spaced placeholder with a defined variable. Done when it is substituted or reported as unresolved, never silently passed through; also consider the replay path noted in the issue.
Written by the indexing model from the issue text.
Description
Summary
The placeholder finder and the placeholder substituter disagree on the syntax. _unresolved_placeholders() accepts whitespace inside the braces ({{ token }}), but _interpolate() only matches the bare form ({{token}}). So a variable that is defined, written as {{ token }}, is neither substituted nor reported as missing — the request goes out with the literal placeholder still in it, silently.
The warning added in 0.1.0 (CHANGELOG.md: "a request that still contains an unresolved {{...}} after substitution now says so instead of sending the placeholder silently") does not fire in exactly the case where the variable exists, so the guarantee it advertises does not hold.
Steps to reproduce
mkdir /tmp/gapi-demo && cd /tmp/gapi-demo
git init -q
git-api
Inside the REPL:
var token=s3cretvalue
var base_url=http://127.0.0.1:8000
GET "/x?t={{ token }}"
The URL must be quoted, otherwise shlex.split splits it on the space before any of this code runs.
Actual output
Two code paths, two different definitions of a placeholder:
text : '/x?t={{token}}'
after interpolate: '/x?t=s3cretvalue'
warned as missing: []
-> substituted? True
text : '/x?t={{ token }}'
after interpolate: '/x?t={{ token }}'
warned as missing: []
-> substituted? False
-> warned? False
text : '/x?t={{ token }}'
after interpolate: '/x?t={{ token }}'
warned as missing: []
-> substituted? False
-> warned? False
So '{{ token }}' is defined-but-never-substituted, and nothing warns:
(no warning printed above)
For contrast, an unknown variable is reported correctly in both spellings:
'/x?t={{nope}}' -> missing=['nope'] (warns fine)
'/x?t={{ nope }}' -> missing=['nope'] (warns fine)
End-to-end through the real REPL, showing the placeholder shipped to the server while the warning system stays quiet:
🌿 Git-API REPL 0.1.0
Type 'help' for commands, 'quit' to exit.
default> Set base_url = http://127.0.0.1:44349
default> Set token = ghp_ab...art2
default>
→ GET http://127.0.0.1:44349/whoami?t={{
← 200
{
"ok": true
}
(use 'save get--whoami?t={{' to save)
default> Bye.
========== SERVER RECEIVED ==========
[
{
"method": "GET",
"path": "/whoami?t={{",
"body": "token"
}
]
The server received /whoami?t={{ with token as the body — the literal placeholder went over the wire, and no ⚠️ Unresolved variable(s) line was printed.
(That run also shows the unquoted form of the same input splitting on whitespace: the URL becomes /whoami?t={{ and token }} becomes a request body. That is the separate shlex.split truncation defect, not this one.)
Expected output
One of two consistent behaviours, both acceptable:
- Whitespace-insensitive substitution —
{{ token }}resolves tos3cretvalue, matching what the finder already accepts; or - Whitespace-sensitive, and then it must warn —
{{ token }}is not a valid placeholder, so it should be reported as unresolved rather than passing through.
What must not happen is the current state: not substituted and not reported. The GET above either resolves the variable or says it could not.
Root cause
git_api/cli.py:92-101 vs git_api/cli.py:115. The two functions use different grammars for the same construct.
Substitution does a literal string replace on the exact key spelling, with no whitespace tolerance:
def _interpolate(text: str, env: dict) -> str:
for key, value in env.items():
if isinstance(value, str):
text = text.replace(f"{{{{{key}}}}}", value) # only "{{token}}"
return text
Detection uses a regex that explicitly allows whitespace, then strips it:
found = re.findall(r"\{\{\s*([^{}]+?)\s*\}\}", text) # also matches "{{ token }}"
...
name = name.strip() # -> "token"
if name and name not in seen and not isinstance(env.get(name), str):
seen.append(name)
The final guard is the trap: _unresolved_placeholders only reports names that are absent from env. For {{ token }} with token defined, the name resolves fine, so the function reports nothing — the caller concludes the text is clean. But _interpolate never touched it, because it only replaces {{token}}. The check that was added specifically to catch leftover placeholders is blind to every placeholder whose variable happens to exist.
Net effect: {{ token }} with token defined is the only combination that is both unsubstituted and unreported. Every other combination either works or warns.
Suggested fix
Make the substituter regex-based so it accepts the same grammar as the finder, and use one compiled pattern for both:
--- a/git_api/cli.py
+++ b/git_api/cli.py
@@
+import re
+
+PLACEHOLDER = re.compile(r"\{\{\s*([^{}]+?)\s*\}\}")
+
+
def _interpolate(text: str, env: dict) -> str:
"""Replace every ``{{var}}`` in *text* with the matching string in *env*.
@@
- for key, value in env.items():
- if isinstance(value, str):
- text = text.replace(f"{{{{{key}}}}}", value)
- return text
+ def _sub(m: re.Match[str]) -> str:
+ value = env.get(m.group(1).strip())
+ return value if isinstance(value, str) else m.group(0)
+
+ return PLACEHOLDER.sub(_sub, text)
@@
- found = re.findall(r"\{\{\s*([^{}]+?)\s*\}\}", text)
+ found = PLACEHOLDER.findall(text)
Returning m.group(0) for a non-string or missing value leaves the placeholder in place, which is what makes the existing _unresolved_placeholders warning fire correctly.
Worth a test for each of the three spellings in the table above, asserting that a defined variable is either substituted or reported — never silently passed through.
Related
- Interacts with the
shlex.splittruncation defect: an unquoted{{ token }}is split before these functions see it, which is why the reproduction has to quote the URL. - Also reachable via
replay, which routes saved URLs through the same_interpolateatcli.py:427— a request saved with a spaced placeholder replays the same broken URL. CHANGELOG.md0.1.0 "Fixed" claims unresolved placeholders now say so; this is the case where that claim does not hold.
- Dominant language
- Python
- Stars
- 0
- Forks
- 1
- Avg merge
- 37m
- Merged PRs (30d)
- 9
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from yunaremaia/git-api
-
bug good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
yunaremaia/git-api#60 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 73/100
yunaremaia/git-api#59 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
yunaremaia/git-api#58 ·
Maintainers usually reply within 1 day
-
bug: a request name containing `-f` silently arms `--force` and overwrites an existing saved requestOpenbug
Difficulty 2/5 1-3 hours Newbie friendliness 91/100
yunaremaia/git-api#52 ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
yunaremaia/git-api#49 ·
Maintainers usually reply within 1 day
All issues in yunaremaia/git-api
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
foxglove/mcap#1907 · 1 comment ·
Maintainers usually reply within 1 day
-
namespace operations
Difficulty 1/5 Under an hour Newbie friendliness 72/100
EclipseFdn/open-vsx.org#14623 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
developmentseed/titiler-cmr#261 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
andrewyng/openworker#774 ·
Maintainers usually reply within 1 day
-
aggfuncOpen
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
worldbank/blackmarblepy#160 ·