pub fn validate_path_placeholder(
param: &str,
value: &str,
rules: &PlaceholderRules<'_>,
) -> Result<(), PlaceholderRefusal>Expand description
The D4 path-placeholder floor, re-exported from core pmcp.
There is exactly ONE implementation of these rules and it lives in
pmcp::server::schema_validation. This is a pub use, never a second copy
(Phase 128, Q2). Two reasons the home is core rather than here:
- The toolkit’s curated
httpbuild has nopmcp-code-modeedge and must not gain one (SC-1), so the shared rule cannot live in this crate. - This repo has a documented three-way-drift incident from a security rule that existed in more than one copy, so a second denylist is a prohibited shape rather than a style preference.
The re-export exists because D-09 obliges the SDK to publish the helper under
the name a third-party HttpExecutor implementor would look for. An
implementor whose template syntax is not OpenAPI’s {key} can call
pmcp_code_mode::validate_path_placeholder on each value it substitutes and
pmcp_code_mode::validate_resolved_path on the composed result — or
pmcp_code_mode::validate_resolved_target, which is that rule widened by the
single author-written ? separator, and is what ResolvedPath::from_checked
itself calls. Either reaches the
same rule the SDK itself applies before calling
HttpExecutor::execute_request. (Plain backticks, not an intra-doc link:
executor is gated on js-runtime and the link would not resolve in a
default-feature doc build.)
Validate ONE path-placeholder value before substitution (D4).
This is the single copy of the D4 character floor in the SDK. It lives in core
pmcp — not in pmcp-code-mode — because BOTH HTTP surfaces must reach it:
the curated single-call build resolves to the toolkit’s http feature, whose
dependency list carries no pmcp-code-mode edge (RESEARCH Finding 6), so a
helper exported only from there would force either a new dependency edge that
widens the curated graph or two copies of one security rule — the drift class
this repo has already been bitten by. D-09’s published-helper obligation is met
by a pub use re-export from pmcp-code-mode.
§The four steps, and why the order is load-bearing
- Unconditional floor, which no declared pattern can relax (D-10). It is
implemented as DECODE ONCE, THEN DENY — never as an enumerated denylist of
literal and pre-encoded spellings, because an enumeration is incomplete by
construction: a mixed form such as
.%2Eor%2E.decodes to the parent-directory sequence while matching neither the literal nor the fully-encoded spelling. Enumerating more spellings does not converge; decoding does. - Always-on cap at
PLACEHOLDER_MAX_LENGTHcode points (D-08). - Declared pattern narrows — evaluated through
cached_input_validator, so a placeholder pattern and aninputSchemapattern resolve\sthrough the identical engine AND a repeated pattern compiles once. - Declared length narrows further; a declared length larger than the module constant never widens it.
Running the floor FIRST is empirically justified, not stylistic: a spec pattern
of ^.*$ accepts every CR-01 payload (measured, RESEARCH Finding 5b), so
pattern-supersedes-floor would have silently disabled the check.
This is a pure function holding no shared mutable state of its own, so two concurrent Code Mode calls on one executor cannot interleave placeholder state.
value is the value ALREADY RENDERED to a string by the caller (the toolkit’s
render_scalar), so the floor and the cap see the rendered text rather than a
JSON number or bool.
§Not sufficient on its own
Per-value checks cannot establish final-path safety. Every caller MUST also run
validate_resolved_path on the composed path before dispatch.
§Errors
Err(PlaceholderRefusal) naming the DECLARED parameter and the DECLARED
expectation, never the value.