Laravel AT Protocol Client (alpha & unstable)
1<?php
2
3namespace SocialDept\AtpClient\Attributes;
4
5use Attribute;
6use SocialDept\AtpClient\Enums\Scope;
7
8/**
9 * Documents that a method requires authentication with specific OAuth scopes.
10 *
11 * This attribute currently serves as documentation to indicate which AT Protocol
12 * endpoints require authentication and what scopes they need. It helps developers
13 * understand scope requirements when building applications.
14 *
15 * While this attribute does not currently perform runtime enforcement, scope
16 * validation will be implemented in a future release. Correctly attributing
17 * endpoints now ensures forward compatibility when enforcement is enabled.
18 *
19 * The AT Protocol currently uses "transition scopes" (like `transition:generic`) while
20 * moving toward more granular scopes. The `granular` parameter allows documenting the
21 * future granular scope that will replace the transition scope.
22 *
23 * @example Basic usage with a transition scope
24 * ```php
25 * #[ScopedEndpoint(Scope::TransitionGeneric)]
26 * public function getTimeline(): GetTimelineResponse
27 * ```
28 *
29 * @example With future granular scope documented
30 * ```php
31 * #[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:app.bsky.feed.getTimeline')]
32 * public function getTimeline(): GetTimelineResponse
33 * ```
34 *
35 * @see \SocialDept\AtpClient\Attributes\PublicEndpoint For endpoints that don't require authentication
36 * @see \SocialDept\AtpClient\Enums\Scope For available scope values
37 */
38#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
39class ScopedEndpoint
40{
41 public array $scopes;
42
43 /**
44 * @param string|Scope|array<string|Scope> $scopes Required scope(s) for this method
45 * @param string|null $granular Future granular scope equivalent
46 * @param string $description Human-readable description of scope requirement
47 */
48 public function __construct(
49 string|Scope|array $scopes,
50 public readonly ?string $granular = null,
51 public readonly string $description = '',
52 ) {
53 $this->scopes = $this->normalizeScopes($scopes);
54 }
55
56 protected function normalizeScopes(string|Scope|array $scopes): array
57 {
58 if (! is_array($scopes)) {
59 $scopes = [$scopes];
60 }
61
62 return array_map(
63 fn ($scope) => $scope instanceof Scope ? $scope->value : $scope,
64 $scopes
65 );
66 }
67}