From 8f9ada1ad7f481d2f0b29a4546c1fa52b304e8b3 Mon Sep 17 00:00:00 2001 From: "Vladyslav G." Date: Wed, 2 Sep 2026 17:33:08 +0200 Subject: [PATCH] fix(rest): return what the endpoints actually answer with MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GuildScheduledEvent::get(), create() and modify() mapped their answer as an array. Discord answers each with one event, and mapArray() declares its parameter as array, so every call died on a TypeError before it returned anything. There was no test file for the class at all, which is how three of its six methods came to have never worked. Eleven more methods had a @return that disagreed with the mapper beside it — getBan and four Webhook methods documented as lists while returning one object, and getVoiceRegions, getInvites, getIntegrations, getChannelInvites, GuildTemplate::list and the two role connection metadata methods documented as one while returning lists. Nothing broke at runtime, but every caller's static analysis was told the wrong shape, which is how this was noticed: a bot reading getBan()->reason was told it was reading a property on an array. --- .../ApplicationRoleConnectionMetadata.php | 4 +- src/Rest/Channel.php | 2 +- src/Rest/Guild.php | 8 +- src/Rest/GuildScheduledEvent.php | 6 +- src/Rest/GuildTemplate.php | 2 +- src/Rest/Webhook.php | 8 +- tests/Rest/GuildScheduledEventTest.php | 92 +++++++++++++++++++ 7 files changed, 107 insertions(+), 15 deletions(-) create mode 100644 tests/Rest/GuildScheduledEventTest.php diff --git a/src/Rest/ApplicationRoleConnectionMetadata.php b/src/Rest/ApplicationRoleConnectionMetadata.php index 68fd080..a4d4080 100644 --- a/src/Rest/ApplicationRoleConnectionMetadata.php +++ b/src/Rest/ApplicationRoleConnectionMetadata.php @@ -16,7 +16,7 @@ class ApplicationRoleConnectionMetadata extends HttpResource /** * @see https://discord.com/developers/docs/resources/application-role-connection-metadata#get-application-role-connection-metadata-records * - * @return PromiseInterface<\Tempcord\Discord\Parts\ApplicationRoleConnectionMetadata> + * @return PromiseInterface<\Tempcord\Discord\Parts\ApplicationRoleConnectionMetadata[]> */ public function getRecords(string $applicationId): PromiseInterface { @@ -34,7 +34,7 @@ public function getRecords(string $applicationId): PromiseInterface /** * @see https://discord.com/developers/docs/resources/application-role-connection-metadata#update-application-role-connection-metadata-records * - * @return PromiseInterface<\Tempcord\Discord\Parts\ApplicationRoleConnectionMetadata> + * @return PromiseInterface<\Tempcord\Discord\Parts\ApplicationRoleConnectionMetadata[]> */ public function updateRecords(string $applicationId, array $params): PromiseInterface { diff --git a/src/Rest/Channel.php b/src/Rest/Channel.php index df9c21d..acf3044 100644 --- a/src/Rest/Channel.php +++ b/src/Rest/Channel.php @@ -389,7 +389,7 @@ public function editChannelPermissions( /** * @see https://discord.com/developers/docs/resources/invite#invite-object * - * @return PromiseInterface<\Tempcord\Discord\Parts\Invite> + * @return PromiseInterface<\Tempcord\Discord\Parts\Invite[]> */ public function getChannelInvites(string $channelId): PromiseInterface { diff --git a/src/Rest/Guild.php b/src/Rest/Guild.php index 212ec7c..2377784 100644 --- a/src/Rest/Guild.php +++ b/src/Rest/Guild.php @@ -415,7 +415,7 @@ public function getBans(string $guildId): PromiseInterface /** * @see https://discord.com/developers/docs/resources/guild#get-guild-ban * - * @return PromiseInterface<\Tempcord\Discord\Parts\GuildBan[]> + * @return PromiseInterface<\Tempcord\Discord\Parts\GuildBan> */ public function getBan(string $guildId, string $userId): PromiseInterface { @@ -659,7 +659,7 @@ public function beginPrune(string $guildId, array $params, ?string $reason = nul /** * @see https://discord.com/developers/docs/resources/guild#get-guild-voice-regions * - * @return PromiseInterface<\Tempcord\Discord\Parts\VoiceRegion> + * @return PromiseInterface<\Tempcord\Discord\Parts\VoiceRegion[]> */ public function getVoiceRegions(string $guildId): PromiseInterface { @@ -714,7 +714,7 @@ public function getUserVoiceState(string $guildId, string $userId): PromiseInter /** * @see https://discord.com/developers/docs/resources/guild#get-guild-invites * - * @return PromiseInterface<\Tempcord\Discord\Parts\Invite> + * @return PromiseInterface<\Tempcord\Discord\Parts\Invite[]> */ public function getInvites(string $guildId): PromiseInterface { @@ -732,7 +732,7 @@ public function getInvites(string $guildId): PromiseInterface /** * @see https://discord.com/developers/docs/resources/guild#get-guild-integrations * - * @return PromiseInterface<\Tempcord\Discord\Parts\Integration> + * @return PromiseInterface<\Tempcord\Discord\Parts\Integration[]> */ public function getIntegrations(string $guildId): PromiseInterface { diff --git a/src/Rest/GuildScheduledEvent.php b/src/Rest/GuildScheduledEvent.php index e469c3d..aa1da90 100644 --- a/src/Rest/GuildScheduledEvent.php +++ b/src/Rest/GuildScheduledEvent.php @@ -40,7 +40,7 @@ public function get(string $guildId, string $scheduledEventId, bool $withUserCou $endpoint = Endpoint::bind(Endpoint::GUILD_SCHEDULED_EVENT, $guildId, $scheduledEventId); $endpoint->addQuery('with_user_count', $withUserCount); - return $this->mapArrayPromise( + return $this->mapPromise( $this->http->get( $endpoint ), @@ -54,7 +54,7 @@ public function get(string $guildId, string $scheduledEventId, bool $withUserCou */ public function create(string $guildId, array $params, ?string $reason = null): PromiseInterface { - return $this->mapArrayPromise( + return $this->mapPromise( $this->http->post( Endpoint::bind(Endpoint::GUILD_SCHEDULED_EVENTS, $guildId), $params, @@ -70,7 +70,7 @@ public function create(string $guildId, array $params, ?string $reason = null): */ public function modify(string $guildId, string $scheduledEventId, array $params, ?string $reason = null): PromiseInterface { - return $this->mapArrayPromise( + return $this->mapPromise( $this->http->patch( Endpoint::bind(Endpoint::GUILD_SCHEDULED_EVENT, $guildId, $scheduledEventId), $params, diff --git a/src/Rest/GuildTemplate.php b/src/Rest/GuildTemplate.php index e358c5b..a95c197 100644 --- a/src/Rest/GuildTemplate.php +++ b/src/Rest/GuildTemplate.php @@ -17,7 +17,7 @@ class GuildTemplate extends HttpResource { /** * @see https://discord.com/developers/docs/resources/guild-template#get-guild-templates - * @return PromiseInterface<\Tempcord\Discord\Parts\GuildTemplate> + * @return PromiseInterface<\Tempcord\Discord\Parts\GuildTemplate[]> */ public function list(string $guildId): PromiseInterface { diff --git a/src/Rest/Webhook.php b/src/Rest/Webhook.php index c1cdb3a..87aec3f 100644 --- a/src/Rest/Webhook.php +++ b/src/Rest/Webhook.php @@ -153,7 +153,7 @@ public function getGuildWebhooks(string $guildId) /** * @see https://discord.com/developers/docs/resources/webhook#get-webhook * - * @return PromiseInterface<\Tempcord\Discord\Parts\Webhook[]> + * @return PromiseInterface<\Tempcord\Discord\Parts\Webhook> */ public function get(string $webhookId) { @@ -171,7 +171,7 @@ public function get(string $webhookId) /** * @see https://discord.com/developers/docs/resources/webhook#get-webhook-with-token * - * @return PromiseInterface<\Tempcord\Discord\Parts\Webhook[]> + * @return PromiseInterface<\Tempcord\Discord\Parts\Webhook> */ public function getWithToken(string $webhookId, string $token) { @@ -190,7 +190,7 @@ public function getWithToken(string $webhookId, string $token) /** * @see https://discord.com/developers/docs/resources/webhook#modify-webhook * - * @return PromiseInterface<\Tempcord\Discord\Parts\Webhook[]> + * @return PromiseInterface<\Tempcord\Discord\Parts\Webhook> */ public function modify(string $webhookId, ModifyWebhookBuilder $builder, ?string $reason = null): PromiseInterface { @@ -210,7 +210,7 @@ public function modify(string $webhookId, ModifyWebhookBuilder $builder, ?string /** * @see https://discord.com/developers/docs/resources/webhook#modify-webhook-with-token * - * @return PromiseInterface<\Tempcord\Discord\Parts\Webhook[]> + * @return PromiseInterface<\Tempcord\Discord\Parts\Webhook> */ public function modifyWithToken(string $webhookId, string $token, ModifyWebhookBuilder $builder, ?string $reason = null): PromiseInterface { diff --git a/tests/Rest/GuildScheduledEventTest.php b/tests/Rest/GuildScheduledEventTest.php new file mode 100644 index 0000000..2e6a6c3 --- /dev/null +++ b/tests/Rest/GuildScheduledEventTest.php @@ -0,0 +1,92 @@ + [ + 'method' => 'list', + 'args' => ['::guild id::'], + 'mockOptions' => [ + 'method' => 'get', + 'return' => [(object) [], (object) []], + ], + 'validationOptions' => [ + 'returnType' => GuildScheduledEvent::class, + 'array' => true, + 'url' => 'guilds/::guild id::/scheduled-events?with_user_count=0', + ] + ], + /* + * These three answer with one event, not a list of them. Mapping + * the answer as an array handed a stdClass to a parameter declared + * array, so every call died on a TypeError before it could return. + */ + 'Get scheduled event' => [ + 'method' => 'get', + 'args' => ['::guild id::', '::event id::'], + 'mockOptions' => [ + 'method' => 'get', + 'return' => (object) ['id' => '::event id::'], + ], + 'validationOptions' => [ + 'returnType' => GuildScheduledEvent::class, + ] + ], + 'Create scheduled event' => [ + 'method' => 'create', + 'args' => ['::guild id::', ['name' => '::name::']], + 'mockOptions' => [ + 'method' => 'post', + 'return' => (object) ['id' => '::event id::'], + ], + 'validationOptions' => [ + 'returnType' => GuildScheduledEvent::class, + ] + ], + 'Modify scheduled event' => [ + 'method' => 'modify', + 'args' => ['::guild id::', '::event id::', ['name' => '::name::']], + 'mockOptions' => [ + 'method' => 'patch', + 'return' => (object) ['id' => '::event id::'], + ], + 'validationOptions' => [ + 'returnType' => GuildScheduledEvent::class, + ] + ], + 'Delete scheduled event' => [ + 'method' => 'delete', + 'args' => ['::guild id::', '::event id::'], + 'mockOptions' => [ + 'method' => 'delete', + 'return' => null, + ], + 'validationOptions' => [] + ], + 'Get scheduled event users' => [ + 'method' => 'getUsers', + 'args' => ['::guild id::', '::event id::'], + 'mockOptions' => [ + 'method' => 'get', + 'return' => [(object) [], (object) []], + ], + 'validationOptions' => [ + 'returnType' => GuildScheduledEventUser::class, + 'array' => true, + ] + ], + ]; + } +}