Skip to content

Support per-section GAM ad unit paths via gam_unit_path templates - #957

Open
prk-Jr wants to merge 25 commits into
mainfrom
954-per-section-gam-unit-path
Open

Support per-section GAM ad unit paths via gam_unit_path templates#957
prk-Jr wants to merge 25 commits into
mainfrom
954-per-section-gam-unit-path

Conversation

@prk-Jr

@prk-Jr prk-Jr commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • creative_opportunities.slot.gam_unit_path is now a template with {network_id}, {section}, and {slot_id} placeholders, so a publisher whose GAM ad unit varies by site section expresses that in one slot rule instead of one rule per (slot × section).
  • {section} is derived from the request path (first segment, sanitized), with a required section_root for / — the URL→section convention lives in config, not core.
  • Server-only change: gam_unit_path reaches googletag.defineSlot client-side but is not in the OpenRTB request, so the wire shape is unchanged and there is no JS change.

Changes

File Change
crates/trusted-server-core/src/creative_opportunities.rs Add UnitTemplatePart, parse_unit_template, sanitize_section, derive_section; slot compiled_unit cache + compile_unit_template/render_gam_unit_path/template_uses_section; config section_root + compile_unit_templates; validate_runtime now enforces section_root when {section} is used and drops the render-time emptiness check
crates/trusted-server-core/src/publisher.rs build_slot_json/build_ad_slots_script take request_path; render the template on both the initial-render and handle_page_bids (SPA) paths
crates/trusted-server-core/src/settings.rs prepare_runtime parses templates via compile_unit_templates before validation, surfacing parse errors as Configuration errors
docs/guide/configuration.md New "Creative Opportunities Configuration" section documenting templating, {section} derivation, the no-decode rule, and unmatched-route behavior
trusted-server.example.toml Document section_root and a templated-slot example
docs/superpowers/specs/, docs/superpowers/plans/ Design spec and implementation plan

Closes

Closes #954

Test plan

  • cargo test-fastly && cargo test-axum (also test-cloudflare, test-spin — all pass)
  • cargo fmt --all -- --check
  • cargo clippy-fastly (remaining clippy targets left to CI)
  • Docs format: cd docs && npm run format
  • JS tests / JS format — no JS change in this PR; left to CI
  • clippy-axum / clippy-cloudflare* / clippy-spin* — left to CI

New tests: template parsing (unknown placeholder, unmatched/nested brace, empty), section derivation (/, single/multi-segment, unsafe/undecoded segment new%20snew_20s), render_gam_unit_path (template, default, verbatim), startup validation (missing/invalid section_root, parse error), and initial-render/SPA equivalence for the same path.

Hardening note

Template parsing and section_root validation are config-derived and run at startup in Settings::prepare_runtime. Invalid enabled config fails startup via Report<TrustedServerError::Configuration> — no panic!, unwrap(), or expect() on the config path. compile_unit_templates rejects malformed templates (unknown placeholder, unmatched/nested brace, empty); validate_runtime rejects a {section} template with a missing or non-[A-Za-z0-9_-] section_root. Regression coverage: compile_unit_templates_surfaces_parse_error, validate_runtime_requires_section_root_when_template_uses_section, validate_runtime_rejects_invalid_section_root, and settings_rejects_creative_opportunity_slot_with_empty_gam_unit_path. Existing static gam_unit_path configs are unaffected (verbatim when no placeholders; /{network_id}/{slot_id} when absent).

Checklist

  • Changes follow CLAUDE.md conventions
  • No unwrap() in production code — use expect("should ...")
  • Uses log macros (not println!)
  • New code has tests
  • No secrets or credentials committed

@prk-Jr prk-Jr self-assigned this Jul 23, 2026
@prk-Jr
prk-Jr requested review from ChristianPavilonis and aram356 and removed request for aram356 July 23, 2026 16:25
prk-Jr and others added 3 commits July 23, 2026 21:55
- Remove now-dead resolved_gam_unit_path (superseded by
  render_gam_unit_path) and its two tests; the default
  /<network_id>/<slot_id> path now lives solely in render_gam_unit_path
- Tighten compile_unit_template and render_gam_unit_path to pub(crate)
- Document that validate_runtime must run after compile_unit_templates
  for the {section} -> section_root check to fire
- Use fictional gam_network_id 99999 in the new build_slot_json test

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

The implementation is generally well-tested and CI is green, but the current change has important configuration-compatibility and inventory-routing risks. In particular, config blobs can break deployment rollback, the template cache can silently discard explicit GAM paths outside the finalized-settings path, and the section derivation policy remains hardcoded despite the originating requirement.

Comment thread crates/trusted-server-core/src/creative_opportunities.rs Outdated
Comment thread crates/trusted-server-core/src/creative_opportunities.rs Outdated
Comment thread crates/trusted-server-core/src/creative_opportunities.rs Outdated
Comment thread crates/trusted-server-core/src/creative_opportunities.rs
Comment thread crates/trusted-server-core/src/creative_opportunities.rs
Comment thread docs/guide/configuration.md Outdated
prk-Jr and others added 2 commits July 29, 2026 11:52
Resolve the review findings on the gam_unit_path templating PR.

- Make section derivation fully publisher-configurable with
  `section_segment` (0-based, default 0), so a locale-prefixed site
  (/en/news/article) selects `news` rather than `en`. Paths with no
  segment at that index fall back to `section_root`. Adds
  `CreativeOpportunitiesConfig::section_for_path` so a call site cannot
  apply one policy knob and forget the other.
- Skip serializing `section_root` and `section_segment` when unset.
  `ts config push` re-serializes the typed config into the pushed blob,
  and these structs use `deny_unknown_fields`, so emitting a null key
  would make a binary rollback fail at config load. Comment both keys
  out of the example config for the same reason.
- Stop conflating "no template" with "not compiled". `compiled_unit:
  None` also covers a slot deserialized or built without
  `compile_unit_templates`, and treating it as absent silently routed
  such slots to /<network_id>/<slot_id> — bidding against the wrong
  inventory. `render_gam_unit_path` now falls back to the raw template,
  and `template_uses_section` reads it too so validation cannot skip the
  `section_root` requirement.
- Reject a blank `gam_network_id` at startup when slots are configured,
  closing the case where `gam_unit_path = "{network_id}"` passes
  validation and renders an empty path into googletag.defineSlot. An
  empty slot list disables the feature, so the id stays unchecked there.
- Fix the documented page_patterns: `/news/*` does not match `/news`, so
  the example lost every section landing page it claimed to serve.

`resolved_gam_unit_path` is deliberately not restored as a deprecated
wrapper: it is the path-independent resolver this issue filed as the
bug, so a shim would silently return the untemplated path. The crate is
`publish = false`; `render_gam_unit_path` and `derive_section` are now
`pub` as the supported replacement, making a build break the failure
mode instead of wrong inventory.

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Reviewed the current head after the previous feedback round. The request-path threading, parser, sanitization, initial/SPA consistency, and prior fixes are generally sound, and CI is green. I found one high rollback-compatibility risk, two medium correctness/resource risks, and one low documentation inconsistency; details are inline.

Comment thread crates/trusted-server-core/src/creative_opportunities.rs
Comment thread crates/trusted-server-core/src/creative_opportunities.rs Outdated
Comment thread crates/trusted-server-core/src/creative_opportunities.rs Outdated
Comment thread docs/guide/configuration.md Outdated

@aram356 aram356 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Reviewed the current head against main. The core design — startup-compiled templates, fail-loud validation, and the raw-path no-decode contract — is sound, and the initial-render and SPA paths cannot diverge: both share build_slot_json, and the SPA path parameter round-trips percent-encoding correctly (encodeURIComponentform_urlencoded decode) before the same normalization. Two documentation corrections are required (both cheap): the placeholder table still describes pre-section_segment behavior, and the rollback note affirmatively claims a clean rollback for configs that would silently render placeholder braces literally on an older binary. Six non-blocking suggestions are inline.

CI Status

  • fmt: PASS
  • clippy (all targets): PASS
  • rust tests (fastly/axum/cloudflare/spin/parity/CLI): PASS
  • js tests (vitest): PASS
  • integration + browser tests, CodeQL: PASS

Comment thread docs/guide/configuration.md Outdated
Comment thread CHANGELOG.md Outdated
Comment thread crates/trusted-server-core/src/creative_opportunities.rs
Comment thread crates/trusted-server-core/src/creative_opportunities.rs
Comment thread docs/guide/configuration.md
Comment thread crates/trusted-server-core/src/creative_opportunities.rs Outdated
Comment thread crates/trusted-server-core/src/publisher.rs Outdated
Comment thread crates/trusted-server-core/src/creative_opportunities.rs Outdated
@prk-Jr
prk-Jr requested a review from aram356 August 3, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

creative_opportunities: express per-section gam_unit_path without one rule per (slot × section)

3 participants