Skip to content

feat(validators/url): support private-use URI scheme redirect URIs (RFC 8252)#60

Merged
Meldiron merged 3 commits into
mainfrom
feat/validators-private-use-schemes
Jul 13, 2026
Merged

feat(validators/url): support private-use URI scheme redirect URIs (RFC 8252)#60
Meldiron merged 3 commits into
mainfrom
feat/validators-private-use-schemes

Conversation

@Meldiron

Copy link
Copy Markdown
Contributor

What

Adds an opt-in allowPrivateUseSchemes flag to the URL validator (packages/validators) so it accepts authority-less private-use URI scheme redirect URIs per RFC 8252 §7.1 (reverse-DNS form), e.g.:

com.raycast-x:/oauth
com.example.app:/oauth2redirect/example-provider

Ported from the read-only mirror PR utopia-php/validators#12, which is redirected here since development happens in the monorepo.

Why

Appwrite Cloud implements OAuth 2.0 Dynamic Client Registration (RFC 7591). Native/desktop clients (e.g. Raycast) register redirect URIs using private-use URI schemes. These are valid URIs (scheme:/path, no authority), but PHP's FILTER_VALIDATE_URL requires an authority component and rejects them, blocking legitimate client registration.

How

  • New trailing constructor flag: public function __construct(..., protected bool $allowPrivateUseSchemes = false). Kept last so existing positional call sites are unaffected.
  • When allowPrivateUseSchemes is on, a value is accepted if it passes the existing filter_var validation or it is a private-use URI scheme redirect URI:
    1. Scheme matches the RFC 3986 grammar (validated explicitly with ctype_*, since parse_url does not enforce it — e.g. it would accept 1com.raycast).
    2. Scheme is a reverse-DNS / private-use scheme (contains a dot), which also excludes standard dotless schemes like http/https.
    3. The authority-less remainder (scheme:/path or scheme:path, plus optional query/fragment) is validated by reusing filter_var with a placeholder authority. The scheme://… authority form is left to the existing logic.
  • allowedSchemes, allowEmpty, and allowFragments continue to apply uniformly, including to private-use URIs.

Backward compatibility

Default behavior is unchanged (allowPrivateUseSchemes defaults to false). All pre-existing tests pass; isValid('http:/example.com') and isValid('example.com') remain false.

Tests

Added testAllowPrivateUseSchemes and testAllowPrivateUseSchemesWithConstraints. Full suite: 135 tests, 878 assertions, green.

🤖 Generated with Claude Code

Adds an opt-in `allowPrivateUseSchemes` flag to the URL validator so it
accepts authority-less private-use URI scheme redirect URIs per RFC 8252
§7.1 (reverse-DNS form), e.g. "com.example.app:/oauth". These are valid
URIs but PHP's FILTER_VALIDATE_URL requires an authority component and
rejects them, blocking OAuth Dynamic Client Registration for native
clients (e.g. Raycast).

The new flag is the trailing constructor argument so existing positional
call sites are unaffected, and defaults to false to preserve behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an opt-in allowPrivateUseSchemes flag to the URL validator so it accepts authority-less private-use URI scheme redirect URIs (e.g. com.raycast-x:/oauth) as required by RFC 8252 §7.1 for native OAuth clients. It also brings several housekeeping improvements across other validators: declare(strict_types=1) additions, #[\Override] attributes, and (string) casts.

  • URL.php: New allowPrivateUseSchemes flag validates candidates that fail filter_var via PHP 8.5's \Uri\Rfc3986\Uri::parse(), checking for a dotted scheme and no host; existing allowedSchemes / allowFragments / allowEmpty constraints are applied uniformly afterwards using parse_url.
  • Globstar.php: Pure-exclusion branch refactored to array_all() (PHP 8.5), which drives the composer.json bump from >=8.0 to >=8.5.
  • CI (tests.yml): Workflow now dynamically selects the PHP version per package (maximum of the package's declared floor and a hardcoded 8.4 baseline), so the validators package is now tested on PHP 8.5 without affecting sibling packages.

Confidence Score: 5/5

Safe to merge; the new flag is off by default and all existing call sites are unaffected. The core validation logic is sound and the test suite is thorough for the path-absolute URI form.

The allowPrivateUseSchemes implementation is correct: \Uri\Rfc3986\Uri::parse() returns null for invalid URIs (confirmed by PHP 8.5 docs), making the instanceof guard work as intended. Default behaviour is unchanged, backward compatibility is preserved, and 135 tests pass. The one open concern (opaque-form constraint coverage) was flagged in a prior review thread and represents a testing gap rather than a defect in the shipped logic. The PHP version floor jump is significant but intentional and documented in composer metadata.

packages/validators/composer.json — the >=8.5 floor is a breaking change for any consumer still on PHP 8.0–8.4 and may warrant a semver major release.

Important Files Changed

Filename Overview
packages/validators/src/Validator/URL.php Adds allowPrivateUseSchemes flag using PHP 8.5's \Uri\Rfc3986\Uri::parse() for RFC 8252 §7.1 redirect URIs; logic is sound for path-absolute form but the allowedSchemes/allowFragments gates use parse_url which may not parse opaque URI schemes reliably
packages/validators/tests/Validator/URLTest.php New tests cover happy paths and edge cases well; constraint composition (allowedSchemes, allowFragments) is tested only against path-absolute form, leaving opaque URI behavior unverified
packages/validators/composer.json PHP minimum version bumped from >=8.0 to >=8.5 — a major breaking change for package consumers, driven by array_all() in Globstar and \Uri\Rfc3986\Uri in URL
packages/validators/src/Validator/Globstar.php Refactors pure-exclusion branch to use array_all() (PHP 8.5) and adds (string) casts for PHP 8.5 strict-types compliance; logic is equivalent to the previous explicit loop
.github/workflows/tests.yml CI dynamically selects PHP version per package (max of declared floor and 8.4 baseline), cleanly handling the validators package's new >=8.5 requirement without affecting other packages
packages/validators/src/Validator/Contains.php Added declare(strict_types=1) and (string) casts to satisfy PHP 8.5 strict mode; no behavioural change
packages/validators/src/Validator/Range.php Added #[\Override] attributes to overridden methods; purely declarative, no behavioural change

Reviews (3): Last reviewed commit: "chore(validators): conform package to PH..." | Re-trigger Greptile

Comment thread packages/validators/tests/Validator/URLTest.php
@Meldiron
Meldiron requested review from abnegate and loks0n as code owners July 13, 2026 09:09
Bumping the package to `php: >=8.5` makes the shared rector config's
`withPhpSets()` auto-detect 8.5 as the target, activating the 8.1→8.5
rule sets. Applying them (plus pint) modernizes the whole package so the
`check` gate passes: `declare(strict_types=1)`, null-to-string arg casts,
`array_all`, arrow-fn return types, `#[\Override]`, and parenthesis-less
`new X()->method()`. Behavior is unchanged — full suite (135 tests, 878
assertions) stays green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Meldiron
Meldiron merged commit 344afce into main Jul 13, 2026
28 checks passed
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.

2 participants