···11+<?php
22+33+namespace SocialDept\AtpClient\Attributes;
44+55+use Attribute;
66+77+/**
88+ * Documents that a method is a public endpoint that does not require authentication.
99+ *
1010+ * This attribute currently serves as documentation to indicate which AT Protocol
1111+ * endpoints can be called without an authenticated session. It helps developers
1212+ * understand which endpoints work with `Atp::public()` against public API endpoints
1313+ * like `https://public.api.bsky.app`.
1414+ *
1515+ * While this attribute does not currently perform runtime enforcement, scope
1616+ * validation will be implemented in a future release. Correctly attributing
1717+ * endpoints now ensures forward compatibility when enforcement is enabled.
1818+ *
1919+ * Public endpoints typically include operations like:
2020+ * - Reading public profiles and posts
2121+ * - Searching actors and content
2222+ * - Resolving handles to DIDs
2323+ * - Accessing repository data (sync endpoints)
2424+ * - Describing servers and feed generators
2525+ *
2626+ * @example Basic usage
2727+ * ```php
2828+ * #[PublicEndpoint]
2929+ * public function getProfile(string $actor): ProfileViewDetailed
3030+ * ```
3131+ *
3232+ * @see \SocialDept\AtpClient\Attributes\ScopedEndpoint For endpoints that require authentication
3333+ */
3434+#[Attribute(Attribute::TARGET_METHOD)]
3535+class PublicEndpoint
3636+{
3737+ /**
3838+ * @param string $description Human-readable description of the endpoint
3939+ */
4040+ public function __construct(
4141+ public readonly string $description = '',
4242+ ) {}
4343+}
+67
src/Attributes/ScopedEndpoint.php
···11+<?php
22+33+namespace SocialDept\AtpClient\Attributes;
44+55+use Attribute;
66+use SocialDept\AtpClient\Enums\Scope;
77+88+/**
99+ * Documents that a method requires authentication with specific OAuth scopes.
1010+ *
1111+ * This attribute currently serves as documentation to indicate which AT Protocol
1212+ * endpoints require authentication and what scopes they need. It helps developers
1313+ * understand scope requirements when building applications.
1414+ *
1515+ * While this attribute does not currently perform runtime enforcement, scope
1616+ * validation will be implemented in a future release. Correctly attributing
1717+ * endpoints now ensures forward compatibility when enforcement is enabled.
1818+ *
1919+ * The AT Protocol currently uses "transition scopes" (like `transition:generic`) while
2020+ * moving toward more granular scopes. The `granular` parameter allows documenting the
2121+ * future granular scope that will replace the transition scope.
2222+ *
2323+ * @example Basic usage with a transition scope
2424+ * ```php
2525+ * #[ScopedEndpoint(Scope::TransitionGeneric)]
2626+ * public function getTimeline(): GetTimelineResponse
2727+ * ```
2828+ *
2929+ * @example With future granular scope documented
3030+ * ```php
3131+ * #[ScopedEndpoint(Scope::TransitionGeneric, granular: 'rpc:app.bsky.feed.getTimeline')]
3232+ * public function getTimeline(): GetTimelineResponse
3333+ * ```
3434+ *
3535+ * @see \SocialDept\AtpClient\Attributes\PublicEndpoint For endpoints that don't require authentication
3636+ * @see \SocialDept\AtpClient\Enums\Scope For available scope values
3737+ */
3838+#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
3939+class ScopedEndpoint
4040+{
4141+ public array $scopes;
4242+4343+ /**
4444+ * @param string|Scope|array<string|Scope> $scopes Required scope(s) for this method
4545+ * @param string|null $granular Future granular scope equivalent
4646+ * @param string $description Human-readable description of scope requirement
4747+ */
4848+ public function __construct(
4949+ string|Scope|array $scopes,
5050+ public readonly ?string $granular = null,
5151+ public readonly string $description = '',
5252+ ) {
5353+ $this->scopes = $this->normalizeScopes($scopes);
5454+ }
5555+5656+ protected function normalizeScopes(string|Scope|array $scopes): array
5757+ {
5858+ if (! is_array($scopes)) {
5959+ $scopes = [$scopes];
6060+ }
6161+6262+ return array_map(
6363+ fn ($scope) => $scope instanceof Scope ? $scope->value : $scope,
6464+ $scopes
6565+ );
6666+ }
6767+}