diff --git a/src/Appwrite/Platform/Tasks/Specs.php b/src/Appwrite/Platform/Tasks/Specs.php index fab1d69b094..ab28b1e4c06 100644 --- a/src/Appwrite/Platform/Tasks/Specs.php +++ b/src/Appwrite/Platform/Tasks/Specs.php @@ -414,23 +414,15 @@ protected function getKeys(): array public function getSDKPlatformsForRouteSecurity(array $routeSecurity): array { - $sdkPlatforms = []; - foreach ($routeSecurity as $value) { - switch ($value) { - case AuthType::SESSION: - $sdkPlatforms[] = APP_SDK_PLATFORM_CLIENT; - break; - case AuthType::JWT: - case AuthType::KEY: - $sdkPlatforms[] = APP_SDK_PLATFORM_SERVER; - break; - case AuthType::ADMIN: - $sdkPlatforms[] = APP_SDK_PLATFORM_CONSOLE; - break; + $platforms = []; + foreach ($routeSecurity as $auth) { + $platform = $auth instanceof AuthType ? $auth->getPlatform() : null; + if ($platform !== null) { + $platforms[] = $platform; } } - return $sdkPlatforms; + return $platforms; } public function action(string $version, string $mode, ?string $git, ?string $message, ?string $branch): void @@ -479,6 +471,18 @@ public function action(string $version, string $mode, ?string $git, ?string $mes throw new Exception('Failed to create specs directory: ' . $specsDir); } + // Resolve full auth arrays through the active task (including subclass platforms). + foreach ($appRoutes as $method) { + foreach ($method as $route) { + $sdks = $route->getLabel('sdk', []); + foreach (\is_array($sdks) ? $sdks : [$sdks] as $sdk) { + if ($sdk instanceof Method) { + $sdk->setPlatforms($this->getSDKPlatformsForRouteSecurity($sdk->getAuth())); + } + } + } + } + foreach ($platforms as $platform) { $routes = []; $models = []; @@ -487,6 +491,10 @@ public function action(string $version, string $mode, ?string $git, ?string $mes foreach ($appRoutes as $key => $method) { foreach ($method as $route) { + if (!$route->getLabel('docs', true) || (bool) $route->getLabel('mock', false) !== $mocks) { + continue; + } + $sdks = $route->getLabel('sdk', false); if (empty($sdks)) { @@ -498,37 +506,11 @@ public function action(string $version, string $mode, ?string $git, ?string $mes } foreach ($sdks as $sdk) { - /** @var Method $sdk */ - $hide = $sdk->isHidden(); - - if ($hide === true || (\is_array($hide) && \in_array($platform, $hide))) { - continue; - } - - $routeSecurity = $sdk->getAuth(); - $sdkPlatforms = $this->getSDKPlatformsForRouteSecurity($routeSecurity); - - if (!$route->getLabel('docs', true)) { - continue; - } - - if ($route->getLabel('mock', false) && !$mocks) { - continue; - } - - if (!$route->getLabel('mock', false) && $mocks) { - continue; - } - - if (empty($sdk->getNamespace())) { - continue; - } - - if (!\in_array($platform, $sdkPlatforms)) { + if (!\in_array($platform, $sdk->getPlatforms(), true)) { continue; } - $routes[] = $route; + $routes[\spl_object_id($route)] = $route; $routeNamespaces[$sdk->getNamespace()] = true; } } @@ -603,7 +585,7 @@ public function action(string $version, string $mode, ?string $git, ?string $mes $models, $keys[$platform], $authCounts[$platform] ?? 0, - $platform + $platform, ]; foreach (['open-api3'] as $format) { diff --git a/src/Appwrite/SDK/AuthType.php b/src/Appwrite/SDK/AuthType.php index c272a49451d..9610fbe57ff 100644 --- a/src/Appwrite/SDK/AuthType.php +++ b/src/Appwrite/SDK/AuthType.php @@ -15,4 +15,14 @@ enum AuthType: string * auth types that make them reachable (ADMIN, KEY, ...). */ case ORGANIZATION = APP_AUTH_TYPE_ORGANIZATION; + + public function getPlatform(): ?string + { + return match ($this) { + self::SESSION => APP_SDK_PLATFORM_CLIENT, + self::JWT, self::KEY => APP_SDK_PLATFORM_SERVER, + self::ADMIN => APP_SDK_PLATFORM_CONSOLE, + self::ORGANIZATION => null, + }; + } } diff --git a/src/Appwrite/SDK/Method.php b/src/Appwrite/SDK/Method.php index 0e14a68fa31..4c1bde21f5d 100644 --- a/src/Appwrite/SDK/Method.php +++ b/src/Appwrite/SDK/Method.php @@ -12,6 +12,9 @@ class Method public static array $errors = []; + /** @var list|null Null derives membership from auth; an explicit empty list stays empty. */ + protected ?array $platforms = null; + /** * Initialise a new SDK method * @@ -202,6 +205,33 @@ public function isHidden(): bool|array return $this->hide; } + /** + * @param list $platforms Auth membership resolved by the active specs producer. + */ + public function setPlatforms(array $platforms): self + { + $this->platforms = $platforms; + return $this; + } + + /** + * @return list Eligible platforms, independent of the currently selected spec platform. + */ + public function getPlatforms(): array + { + $hide = $this->isHidden(); + if ($hide === true || empty($this->getNamespace())) { + return []; + } + + $platforms = $this->platforms ?? \array_filter(\array_map( + fn ($auth) => $auth instanceof AuthType ? $auth->getPlatform() : null, + $this->getAuth() + )); + + return \array_values(\array_unique(\array_diff($platforms, \is_array($hide) ? $hide : []))); + } + public function isPackaging(): bool { return $this->packaging; diff --git a/src/Appwrite/SDK/Specification/Format/OpenAPI3.php b/src/Appwrite/SDK/Specification/Format/OpenAPI3.php index 593c8d9c9c1..3d758c7bb2e 100644 --- a/src/Appwrite/SDK/Specification/Format/OpenAPI3.php +++ b/src/Appwrite/SDK/Specification/Format/OpenAPI3.php @@ -2,7 +2,6 @@ namespace Appwrite\SDK\Specification\Format; -use Appwrite\Platform\Tasks\Specs; use Appwrite\SDK\AuthType; use Appwrite\SDK\ContentType; use Appwrite\SDK\Method; @@ -245,9 +244,19 @@ public function parse(): array continue; } + $sdkPlatforms = []; + foreach (\is_array($sdk) ? $sdk : [$sdk] as $method) { + $sdkPlatforms = \array_merge($sdkPlatforms, $method->getPlatforms()); + } + $sdkPlatforms = \array_values(\array_unique($sdkPlatforms)); + if (!\in_array($this->platform, $sdkPlatforms, true)) { + continue; + } + $additionalMethods = null; if (\is_array($sdk)) { $additionalMethods = $sdk; + // Keep the original base descriptor's schemas and auth, even when only a sibling is eligible. $sdk = $sdk[0]; } @@ -260,12 +269,6 @@ public function parse(): array $desc = $sdk->getDescriptionFilePath() ?: $sdk->getDescription(); $produces = ($sdk->getContentType())->value; - $routeSecurity = $sdk->getAuth(); - - $specs = new Specs(); - $sdkPlatforms = $specs->getSDKPlatformsForRouteSecurity($routeSecurity); - - $sdkPlatforms = array_values(array_unique($sdkPlatforms)); $namespace = $sdk->getNamespace(); $descContents = $this->getDescriptionContents($desc); @@ -303,10 +306,9 @@ public function parse(): array /** @var Method $methodObj */ $desc = $methodObj->getDescriptionFilePath(); - $methodSecurities = $methodObj->getAuth(); - $methodSdkPlatforms = $specs->getSDKPlatformsForRouteSecurity($methodSecurities); + $methodSdkPlatforms = $methodObj->getPlatforms(); - if (!\in_array($this->platform, $methodSdkPlatforms)) { + if (!\in_array($this->platform, $methodSdkPlatforms, true)) { continue; } @@ -320,6 +322,7 @@ public function parse(): array $additionalMethod = [ 'name' => $methodObj->getMethodName(), 'namespace' => $methodObj->getNamespace(), + 'platforms' => $methodSdkPlatforms, 'desc' => $methodObj->getDesc(), 'auth' => \array_slice($methodSecurities, 0, $this->authCount), 'parameters' => [], diff --git a/tests/unit/SDK/Specification/FormatTest.php b/tests/unit/SDK/Specification/FormatTest.php index 61754fbf57d..f940edda7e6 100644 --- a/tests/unit/SDK/Specification/FormatTest.php +++ b/tests/unit/SDK/Specification/FormatTest.php @@ -173,7 +173,7 @@ public function testUnionWithAFreeStringBranchEmitsAnyOf(): void group: null, name: 'listTests', description: 'List tests.', - auth: [], + auth: [AuthType::ADMIN], responses: [], )) ->param('metrics', [], new AnyOf([ @@ -211,7 +211,7 @@ public function testClosedEnumEmitsAnnotatedBranchesInRequestBody(): void group: null, name: 'createTest', description: 'Create test.', - auth: [], + auth: [AuthType::ADMIN], responses: [], )) ->param('kind', 'basic', new WhiteList(['basic', 'advanced']), 'Test kind.', enum: new Enum( @@ -244,7 +244,7 @@ public function testEnumNameMustNotOverlapServiceName(): void group: null, name: 'createTest', description: 'Create test.', - auth: [], + auth: [AuthType::ADMIN], responses: [], )) ->param('kind', 'basic', new WhiteList(['basic', 'advanced']), 'Test kind.', enum: new Enum()); @@ -277,7 +277,7 @@ public function testResponseModelEnumEmitsAnnotatedBranches(): void group: null, name: 'get', description: 'Get health.', - auth: [], + auth: [AuthType::ADMIN], responses: [new SDKResponse(code: Response::STATUS_CODE_OK, model: Response::MODEL_HEALTH_STATUS)], )); @@ -306,7 +306,7 @@ public function testOpenApiCustomIdBodyFieldIncludesIdGeneratorMetadata(): void group: null, name: 'createTest', description: 'Create test.', - auth: [], + auth: [AuthType::ADMIN], responses: [], )) ->param('userId', '', new CustomId(), 'User ID.'); @@ -332,7 +332,7 @@ public function testOpenApiExamplesUseNativeSchemaTypes(): void group: null, name: 'createTest', description: 'Create test.', - auth: [], + auth: [AuthType::ADMIN], responses: [], )) ->param('metadata', [], new Assoc(), 'Metadata.', example: '{"enabled":true}') @@ -367,7 +367,7 @@ public function testArrayListItemTypesAreValidOpenApiTypes(): void group: null, name: 'createTest', description: 'Create test.', - auth: [], + auth: [AuthType::ADMIN], responses: [], )) ->param('percents', [], new ArrayList(new FloatValidator()), 'Percents.', optional: true) @@ -397,7 +397,7 @@ public function testMethodParameterOverridesFilterAndReplaceRouteParams(): void group: null, name: 'createTestWithOverrides', description: 'Create test.', - auth: [], + auth: [AuthType::ADMIN], responses: [], parameters: [ new Parameter('engine', hide: true), @@ -426,7 +426,7 @@ public function testMethodParameterNullDefaultOverridesRouteDefault(): void group: null, name: 'createTestWithNullDefault', description: 'Create test.', - auth: [], + auth: [AuthType::ADMIN], responses: [], parameters: [ new Parameter('engine', default: null), @@ -470,7 +470,7 @@ public function testDeleteRouteOptionalParamsAreQueryParams(): void group: null, name: 'deleteTest', description: 'Delete test.', - auth: [], + auth: [AuthType::ADMIN], responses: [], )) ->param('testId', '', new Text(256), 'Test ID.') @@ -503,7 +503,7 @@ public function getMethods(): array group: null, name: 'getOrUpdateTest', description: 'Get or update test.', - auth: [], + auth: [AuthType::ADMIN], responses: [], )) ->param('testId', '', new Text(256), 'Test ID.') @@ -536,7 +536,7 @@ public function testModelReferencesDoNotEmitItemsOnObjectProperties(): void group: null, name: 'getTeamTest', description: 'Get test.', - auth: [], + auth: [AuthType::ADMIN], responses: [ new SDKResponse( code: 200, @@ -614,7 +614,7 @@ public function getType(): string group: null, name: 'getParent', description: 'Get parent.', - auth: [], + auth: [AuthType::ADMIN], responses: [ new SDKResponse( code: 200, @@ -642,7 +642,7 @@ public function testExplicitEmptyArrayExampleIsPreserved(): void group: null, name: 'getError', description: 'Get error.', - auth: [], + auth: [AuthType::ADMIN], responses: [ new SDKResponse( code: 500, @@ -670,7 +670,7 @@ public function testOptionalPathParameterIsEmittedAsRequired(): void group: null, name: 'getPathTest', description: 'Get test.', - auth: [], + auth: [AuthType::ADMIN], responses: [ new SDKResponse(code: 200, model: Response::MODEL_NONE), ], @@ -711,7 +711,7 @@ public function testUsageProjectEmbeddingsTextSchema(): void group: null, name: 'getUsageTest', description: 'Get test.', - auth: [], + auth: [AuthType::ADMIN], responses: [ new SDKResponse( code: 200, @@ -755,7 +755,7 @@ public function testJsonArrayModelExamplesUseArraySchemas(): void group: null, name: 'getMigration', description: 'Get migration.', - auth: [], + auth: [AuthType::ADMIN], responses: [ new SDKResponse( code: 200, @@ -823,7 +823,7 @@ public function testMultiTypePropertiesWrapOneOfInAllOf(): void group: null, name: 'getUserTest', description: 'Get test.', - auth: [], + auth: [AuthType::ADMIN], responses: [ new SDKResponse( code: 200, @@ -868,7 +868,7 @@ public function testArraySchemasEmitItems(): void group: null, name: 'createSpatialTest', description: 'Create spatial test.', - auth: [], + auth: [AuthType::ADMIN], responses: [], )) ->param('default', null, new Nullable(new Spatial(Database::VAR_LINESTRING)), 'Default value.', true); @@ -880,7 +880,7 @@ public function testArraySchemasEmitItems(): void group: null, name: 'getSpatialTest', description: 'Get spatial test.', - auth: [], + auth: [AuthType::ADMIN], responses: [ new SDKResponse( code: 200, @@ -916,7 +916,7 @@ public function testPasswordFormatMarksOnlyExplicitPasswordFields(): void group: null, name: 'createTest', description: 'Create test.', - auth: [], + auth: [AuthType::ADMIN], responses: [ new SDKResponse( code: 200, @@ -959,7 +959,7 @@ public function testNoContentMethodsKeepProducesMetadata(): void group: null, name: 'deleteTest', description: 'Delete test.', - auth: [], + auth: [AuthType::ADMIN], responses: [ new SDKResponse( code: 204, @@ -989,7 +989,7 @@ public function testBinaryResponsesEmitResponseContent(): void group: null, name: 'getTestIcon', description: 'Get test icon.', - auth: [], + auth: [AuthType::ADMIN], responses: [ new SDKResponse( code: 200, @@ -1022,7 +1022,7 @@ public function testAdditionalParametersAreIncludedInRequestBody(): void group: null, name: 'queryTest', description: 'GraphQL test endpoint.', - auth: [], + auth: [AuthType::ADMIN], responses: [], additionalParameters: [ 'query' => [ @@ -1053,7 +1053,7 @@ public function testJsonAndNullableModelRulesEmitExpectedSchemas(): void group: null, name: 'getTestProvider', description: 'Get test provider.', - auth: [], + auth: [AuthType::ADMIN], responses: [ new SDKResponse( code: 200, @@ -1086,7 +1086,7 @@ public function testJsonAndNullableModelRulesEmitExpectedSchemas(): void group: null, name: 'get' . \ucfirst($model), description: 'Get test response model.', - auth: [], + auth: [AuthType::ADMIN], responses: [ new SDKResponse( code: 200, @@ -1132,7 +1132,7 @@ public function testQueriesSubclassesEmitArrayOfStrings(): void group: null, name: 'listTests', description: 'List tests.', - auth: [], + auth: [AuthType::ADMIN], responses: [], )) ->param('queries', [], new Queries([new Limit(), new Offset()]), 'Queries.', true) @@ -1164,7 +1164,7 @@ public function testZeroIsKeptAsADeclaredExample(): void group: null, name: 'createTest', description: 'Create test.', - auth: [], + auth: [AuthType::ADMIN], responses: [], )) ->param('min', 0, new Range(0, 100), 'Minimum.', example: '0') @@ -1192,7 +1192,7 @@ public function testValidatorsWithoutAnExampleFallBackToOne(): void group: null, name: 'createTest', description: 'Create test.', - auth: [], + auth: [AuthType::ADMIN], responses: [], )) ->param('key', '', new Key(), 'Column key.')