diff options
author | Maxence Lange <maxence@artificial-owl.com> | 2024-12-16 14:26:01 -0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-12-16 14:26:01 -0100 |
commit | d399d47d9be520f71b737dd61f64d2a2ee940c5c (patch) | |
tree | ac029831479bcd98982fd16a62c36d0ed0f4914e /lib | |
parent | f9ee3505a073c84e86c3d76485096883e2fff7e6 (diff) | |
parent | 815991741d2001f7610047ef9627283037c1beec (diff) | |
download | nextcloud-server-d399d47d9be520f71b737dd61f64d2a2ee940c5c.tar.gz nextcloud-server-d399d47d9be520f71b737dd61f64d2a2ee940c5c.zip |
Merge pull request #49399 from nextcloud/feature/noid/config-lexicon
feat(config): implementation of lexicon
Diffstat (limited to 'lib')
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 3 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 3 | ||||
-rw-r--r-- | lib/private/AppConfig.php | 122 | ||||
-rw-r--r-- | lib/private/AppFramework/Bootstrap/RegistrationContext.php | 34 | ||||
-rw-r--r-- | lib/private/Config/UserConfig.php | 107 | ||||
-rw-r--r-- | lib/public/AppFramework/Bootstrap/IRegistrationContext.php | 11 | ||||
-rw-r--r-- | lib/public/IAppConfig.php | 3 | ||||
-rw-r--r-- | lib/unstable/Config/Lexicon/ConfigLexiconEntry.php | 189 | ||||
-rw-r--r-- | lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php | 30 | ||||
-rw-r--r-- | lib/unstable/Config/Lexicon/IConfigLexicon.php | 44 | ||||
-rw-r--r-- | lib/unstable/Config/ValueType.php | 25 |
11 files changed, 570 insertions, 1 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 528bf3bb2da..aa850c8bed4 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -11,6 +11,9 @@ return array( 'NCU\\Config\\Exceptions\\TypeConflictException' => $baseDir . '/lib/unstable/Config/Exceptions/TypeConflictException.php', 'NCU\\Config\\Exceptions\\UnknownKeyException' => $baseDir . '/lib/unstable/Config/Exceptions/UnknownKeyException.php', 'NCU\\Config\\IUserConfig' => $baseDir . '/lib/unstable/Config/IUserConfig.php', + 'NCU\\Config\\Lexicon\\ConfigLexiconEntry' => $baseDir . '/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php', + 'NCU\\Config\\Lexicon\\ConfigLexiconStrictness' => $baseDir . '/lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php', + 'NCU\\Config\\Lexicon\\IConfigLexicon' => $baseDir . '/lib/unstable/Config/Lexicon/IConfigLexicon.php', 'NCU\\Config\\ValueType' => $baseDir . '/lib/unstable/Config/ValueType.php', 'NCU\\Federation\\ISignedCloudFederationProvider' => $baseDir . '/lib/unstable/Federation/ISignedCloudFederationProvider.php', 'NCU\\Security\\Signature\\Enum\\DigestAlgorithm' => $baseDir . '/lib/unstable/Security/Signature/Enum/DigestAlgorithm.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 43b5042c8b4..fd4993dd5a8 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -52,6 +52,9 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'NCU\\Config\\Exceptions\\TypeConflictException' => __DIR__ . '/../../..' . '/lib/unstable/Config/Exceptions/TypeConflictException.php', 'NCU\\Config\\Exceptions\\UnknownKeyException' => __DIR__ . '/../../..' . '/lib/unstable/Config/Exceptions/UnknownKeyException.php', 'NCU\\Config\\IUserConfig' => __DIR__ . '/../../..' . '/lib/unstable/Config/IUserConfig.php', + 'NCU\\Config\\Lexicon\\ConfigLexiconEntry' => __DIR__ . '/../../..' . '/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php', + 'NCU\\Config\\Lexicon\\ConfigLexiconStrictness' => __DIR__ . '/../../..' . '/lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php', + 'NCU\\Config\\Lexicon\\IConfigLexicon' => __DIR__ . '/../../..' . '/lib/unstable/Config/Lexicon/IConfigLexicon.php', 'NCU\\Config\\ValueType' => __DIR__ . '/../../..' . '/lib/unstable/Config/ValueType.php', 'NCU\\Federation\\ISignedCloudFederationProvider' => __DIR__ . '/../../..' . '/lib/unstable/Federation/ISignedCloudFederationProvider.php', 'NCU\\Security\\Signature\\Enum\\DigestAlgorithm' => __DIR__ . '/../../..' . '/lib/unstable/Security/Signature/Enum/DigestAlgorithm.php', diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php index dc9bac7745d..71c958703e9 100644 --- a/lib/private/AppConfig.php +++ b/lib/private/AppConfig.php @@ -11,6 +11,10 @@ namespace OC; use InvalidArgumentException; use JsonException; +use NCU\Config\Lexicon\ConfigLexiconEntry; +use NCU\Config\Lexicon\ConfigLexiconStrictness; +use NCU\Config\Lexicon\IConfigLexicon; +use OC\AppFramework\Bootstrap\Coordinator; use OCP\DB\Exception as DBException; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Exceptions\AppConfigIncorrectTypeException; @@ -55,6 +59,8 @@ class AppConfig implements IAppConfig { private array $valueTypes = []; // type for all config values private bool $fastLoaded = false; private bool $lazyLoaded = false; + /** @var array<array-key, array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */ + private array $configLexiconDetails = []; /** * $migrationCompleted is only needed to manage the previous structure @@ -430,6 +436,9 @@ class AppConfig implements IAppConfig { int $type, ): string { $this->assertParams($app, $key, valueType: $type); + if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $default)) { + return $default; // returns default if strictness of lexicon is set to WARNING (block and report) + } $this->loadConfig($app, $lazy); /** @@ -721,6 +730,9 @@ class AppConfig implements IAppConfig { int $type, ): bool { $this->assertParams($app, $key); + if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type)) { + return false; // returns false as database is not updated + } $this->loadConfig(null, $lazy); $sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type); @@ -1559,4 +1571,114 @@ class AppConfig implements IAppConfig { public function clearCachedConfig(): void { $this->clearCache(); } + + /** + * match and apply current use of config values with defined lexicon + * + * @throws AppConfigUnknownKeyException + * @throws AppConfigTypeConflictException + * @return bool TRUE if everything is fine compared to lexicon or lexicon does not exist + */ + private function matchAndApplyLexiconDefinition( + string $app, + string $key, + bool &$lazy, + int &$type, + string &$default = '', + ): bool { + if (in_array($key, + [ + 'enabled', + 'installed_version', + 'types', + ])) { + return true; // we don't break stuff for this list of config keys. + } + $configDetails = $this->getConfigDetailsFromLexicon($app); + if (!array_key_exists($key, $configDetails['entries'])) { + return $this->applyLexiconStrictness( + $configDetails['strictness'], + 'The app config key ' . $app . '/' . $key . ' is not defined in the config lexicon' + ); + } + + /** @var ConfigLexiconEntry $configValue */ + $configValue = $configDetails['entries'][$key]; + $type &= ~self::VALUE_SENSITIVE; + + $appConfigValueType = $configValue->getValueType()->toAppConfigFlag(); + if ($type === self::VALUE_MIXED) { + $type = $appConfigValueType; // we overwrite if value was requested as mixed + } elseif ($appConfigValueType !== $type) { + throw new AppConfigTypeConflictException('The app config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon'); + } + + $lazy = $configValue->isLazy(); + $default = $configValue->getDefault() ?? $default; // default from Lexicon got priority + if ($configValue->isFlagged(self::FLAG_SENSITIVE)) { + $type |= self::VALUE_SENSITIVE; + } + if ($configValue->isDeprecated()) { + $this->logger->notice('App config key ' . $app . '/' . $key . ' is set as deprecated.'); + } + + return true; + } + + /** + * manage ConfigLexicon behavior based on strictness set in IConfigLexicon + * + * @param ConfigLexiconStrictness|null $strictness + * @param string $line + * + * @return bool TRUE if conflict can be fully ignored, FALSE if action should be not performed + * @throws AppConfigUnknownKeyException if strictness implies exception + * @see IConfigLexicon::getStrictness() + */ + private function applyLexiconStrictness( + ?ConfigLexiconStrictness $strictness, + string $line = '', + ): bool { + if ($strictness === null) { + return true; + } + + switch ($strictness) { + case ConfigLexiconStrictness::IGNORE: + return true; + case ConfigLexiconStrictness::NOTICE: + $this->logger->notice($line); + return true; + case ConfigLexiconStrictness::WARNING: + $this->logger->warning($line); + return false; + } + + throw new AppConfigUnknownKeyException($line); + } + + /** + * extract details from registered $appId's config lexicon + * + * @param string $appId + * + * @return array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness} + */ + private function getConfigDetailsFromLexicon(string $appId): array { + if (!array_key_exists($appId, $this->configLexiconDetails)) { + $entries = []; + $bootstrapCoordinator = \OCP\Server::get(Coordinator::class); + $configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId); + foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) { + $entries[$configEntry->getKey()] = $configEntry; + } + + $this->configLexiconDetails[$appId] = [ + 'entries' => $entries, + 'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE + ]; + } + + return $this->configLexiconDetails[$appId]; + } } diff --git a/lib/private/AppFramework/Bootstrap/RegistrationContext.php b/lib/private/AppFramework/Bootstrap/RegistrationContext.php index d7a380f9e1d..f3b612edc38 100644 --- a/lib/private/AppFramework/Bootstrap/RegistrationContext.php +++ b/lib/private/AppFramework/Bootstrap/RegistrationContext.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace OC\AppFramework\Bootstrap; use Closure; +use NCU\Config\Lexicon\IConfigLexicon; use OC\Support\CrashReport\Registry; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IRegistrationContext; @@ -141,6 +142,9 @@ class RegistrationContext { /** @var ServiceRegistration<IDeclarativeSettingsForm>[] */ private array $declarativeSettings = []; + /** @var array<array-key, string> */ + private array $configLexiconClasses = []; + /** @var ServiceRegistration<ITeamResourceProvider>[] */ private array $teamResourceProviders = []; @@ -422,6 +426,13 @@ class RegistrationContext { $class ); } + + public function registerConfigLexicon(string $configLexiconClass): void { + $this->context->registerConfigLexicon( + $this->appId, + $configLexiconClass + ); + } }; } @@ -622,6 +633,13 @@ class RegistrationContext { } /** + * @psalm-param class-string<IConfigLexicon> $configLexiconClass + */ + public function registerConfigLexicon(string $appId, string $configLexiconClass): void { + $this->configLexiconClasses[$appId] = $configLexiconClass; + } + + /** * @param App[] $apps */ public function delegateCapabilityRegistrations(array $apps): void { @@ -972,4 +990,20 @@ class RegistrationContext { public function getMailProviders(): array { return $this->mailProviders; } + + /** + * returns IConfigLexicon registered by the app. + * null if none registered. + * + * @param string $appId + * + * @return IConfigLexicon|null + */ + public function getConfigLexicon(string $appId): ?IConfigLexicon { + if (!array_key_exists($appId, $this->configLexiconClasses)) { + return null; + } + + return \OCP\Server::get($this->configLexiconClasses[$appId]); + } } diff --git a/lib/private/Config/UserConfig.php b/lib/private/Config/UserConfig.php index 37e109b2121..78c43fc4321 100644 --- a/lib/private/Config/UserConfig.php +++ b/lib/private/Config/UserConfig.php @@ -15,7 +15,10 @@ use NCU\Config\Exceptions\IncorrectTypeException; use NCU\Config\Exceptions\TypeConflictException; use NCU\Config\Exceptions\UnknownKeyException; use NCU\Config\IUserConfig; +use NCU\Config\Lexicon\ConfigLexiconEntry; +use NCU\Config\Lexicon\ConfigLexiconStrictness; use NCU\Config\ValueType; +use OC\AppFramework\Bootstrap\Coordinator; use OCP\DB\Exception as DBException; use OCP\DB\IResult; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -63,6 +66,8 @@ class UserConfig implements IUserConfig { private array $fastLoaded = []; /** @var array<string, boolean> ['user_id' => bool] */ private array $lazyLoaded = []; + /** @var array<array-key, array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness}> ['app_id' => ['strictness' => ConfigLexiconStrictness, 'entries' => ['config_key' => ConfigLexiconEntry[]]] */ + private array $configLexiconDetails = []; public function __construct( protected IDBConnection $connection, @@ -706,6 +711,9 @@ class UserConfig implements IUserConfig { ValueType $type, ): string { $this->assertParams($userId, $app, $key); + if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, default: $default)) { + return $default; // returns default if strictness of lexicon is set to WARNING (block and report) + } $this->loadConfig($userId, $lazy); /** @@ -1038,6 +1046,9 @@ class UserConfig implements IUserConfig { ValueType $type, ): bool { $this->assertParams($userId, $app, $key); + if (!$this->matchAndApplyLexiconDefinition($app, $key, $lazy, $type, $flags)) { + return false; // returns false as database is not updated + } $this->loadConfig($userId, $lazy); $inserted = $refreshCache = false; @@ -1045,7 +1056,7 @@ class UserConfig implements IUserConfig { $sensitive = $this->isFlagged(self::FLAG_SENSITIVE, $flags); if ($sensitive || ($this->hasKey($userId, $app, $key, $lazy) && $this->isSensitive($userId, $app, $key, $lazy))) { $value = self::ENCRYPTION_PREFIX . $this->crypto->encrypt($value); - $flags |= UserConfig::FLAG_SENSITIVE; + $flags |= self::FLAG_SENSITIVE; } // if requested, we fill the 'indexed' field with current value @@ -1803,4 +1814,98 @@ class UserConfig implements IUserConfig { ]); } } + + /** + * match and apply current use of config values with defined lexicon + * + * @throws UnknownKeyException + * @throws TypeConflictException + */ + private function matchAndApplyLexiconDefinition( + string $app, + string $key, + bool &$lazy, + ValueType &$type, + int &$flags = 0, + string &$default = '', + ): bool { + $configDetails = $this->getConfigDetailsFromLexicon($app); + if (!array_key_exists($key, $configDetails['entries'])) { + return $this->applyLexiconStrictness($configDetails['strictness'], 'The user config key ' . $app . '/' . $key . ' is not defined in the config lexicon'); + } + + /** @var ConfigLexiconEntry $configValue */ + $configValue = $configDetails['entries'][$key]; + if ($type === ValueType::MIXED) { + $type = $configValue->getValueType(); // we overwrite if value was requested as mixed + } elseif ($configValue->getValueType() !== $type) { + throw new TypeConflictException('The user config key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon'); + } + + $lazy = $configValue->isLazy(); + $default = $configValue->getDefault() ?? $default; // default from Lexicon got priority + $flags = $configValue->getFlags(); + + if ($configValue->isDeprecated()) { + $this->logger->notice('User config key ' . $app . '/' . $key . ' is set as deprecated.'); + } + + return true; + } + + /** + * manage ConfigLexicon behavior based on strictness set in IConfigLexicon + * + * @see IConfigLexicon::getStrictness() + * @param ConfigLexiconStrictness|null $strictness + * @param string $line + * + * @return bool TRUE if conflict can be fully ignored + * @throws UnknownKeyException + */ + private function applyLexiconStrictness(?ConfigLexiconStrictness $strictness, string $line = ''): bool { + if ($strictness === null) { + return true; + } + + switch ($strictness) { + case ConfigLexiconStrictness::IGNORE: + return true; + case ConfigLexiconStrictness::NOTICE: + $this->logger->notice($line); + return true; + case ConfigLexiconStrictness::WARNING: + $this->logger->warning($line); + return false; + case ConfigLexiconStrictness::EXCEPTION: + throw new UnknownKeyException($line); + } + + throw new UnknownKeyException($line); + } + + /** + * extract details from registered $appId's config lexicon + * + * @param string $appId + * + * @return array{entries: array<array-key, ConfigLexiconEntry>, strictness: ConfigLexiconStrictness} + */ + private function getConfigDetailsFromLexicon(string $appId): array { + if (!array_key_exists($appId, $this->configLexiconDetails)) { + $entries = []; + $bootstrapCoordinator = \OCP\Server::get(Coordinator::class); + $configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId); + foreach ($configLexicon?->getUserConfigs() ?? [] as $configEntry) { + $entries[$configEntry->getKey()] = $configEntry; + } + + $this->configLexiconDetails[$appId] = [ + 'entries' => $entries, + 'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE + ]; + } + + return $this->configLexiconDetails[$appId]; + } } diff --git a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php index b9e5413e5c2..8a18ec8ae9d 100644 --- a/lib/public/AppFramework/Bootstrap/IRegistrationContext.php +++ b/lib/public/AppFramework/Bootstrap/IRegistrationContext.php @@ -423,4 +423,15 @@ interface IRegistrationContext { */ public function registerMailProvider(string $class): void; + + /** + * Register an implementation of \OCP\Config\Lexicon\IConfigLexicon that + * will handle the config lexicon + * + * @param string $configLexiconClass + * + * @psalm-param class-string<\NCU\Config\Lexicon\IConfigLexicon> $configLexiconClass + * @since 31.0.0 + */ + public function registerConfigLexicon(string $configLexiconClass): void; } diff --git a/lib/public/IAppConfig.php b/lib/public/IAppConfig.php index fe894da8d31..d4d5c1c09c7 100644 --- a/lib/public/IAppConfig.php +++ b/lib/public/IAppConfig.php @@ -45,6 +45,9 @@ interface IAppConfig { /** @since 29.0.0 */ public const VALUE_ARRAY = 64; + /** @since 31.0.0 */ + public const FLAG_SENSITIVE = 1; // value is sensitive + /** * Get list of all apps that have at least one config value stored in database * diff --git a/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php b/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php new file mode 100644 index 00000000000..e6c6579881d --- /dev/null +++ b/lib/unstable/Config/Lexicon/ConfigLexiconEntry.php @@ -0,0 +1,189 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace NCU\Config\Lexicon; + +use NCU\Config\ValueType; + +/** + * Model that represent config values within an app config lexicon. + * + * @see IConfigLexicon + * @experimental 31.0.0 + */ +class ConfigLexiconEntry { + private string $definition = ''; + private ?string $default = null; + + /** + * @param string $key config key + * @param ValueType $type type of config value + * @param string $definition optional description of config key available when using occ command + * @param bool $lazy set config value as lazy + * @param int $flags set flags + * @param bool $deprecated set config key as deprecated + * + * @experimental 31.0.0 + * @psalm-suppress PossiblyInvalidCast + * @psalm-suppress RiskyCast + */ + public function __construct( + private readonly string $key, + private readonly ValueType $type, + null|string|int|float|bool|array $default = null, + string $definition = '', + private readonly bool $lazy = false, + private readonly int $flags = 0, + private readonly bool $deprecated = false, + ) { + if ($default !== null) { + // in case $default is array but is not expected to be an array... + $default = ($type !== ValueType::ARRAY && is_array($default)) ? json_encode($default) : $default; + $this->default = match ($type) { + ValueType::MIXED => (string)$default, + ValueType::STRING => $this->convertFromString((string)$default), + ValueType::INT => $this->convertFromInt((int)$default), + ValueType::FLOAT => $this->convertFromFloat((float)$default), + ValueType::BOOL => $this->convertFromBool((bool)$default), + ValueType::ARRAY => $this->convertFromArray((array)$default) + }; + } + + /** @psalm-suppress UndefinedClass */ + if (\OC::$CLI) { // only store definition if ran from CLI + $this->definition = $definition; + } + } + + /** + * @inheritDoc + * + * @return string config key + * @experimental 31.0.0 + */ + public function getKey(): string { + return $this->key; + } + + /** + * @inheritDoc + * + * @return ValueType + * @experimental 31.0.0 + */ + public function getValueType(): ValueType { + return $this->type; + } + + /** + * @param string $default + * @return string + * @experimental 31.0.0 + */ + private function convertFromString(string $default): string { + return $default; + } + + /** + * @param int $default + * @return string + * @experimental 31.0.0 + */ + private function convertFromInt(int $default): string { + return (string)$default; + } + + /** + * @param float $default + * @return string + * @experimental 31.0.0 + */ + private function convertFromFloat(float $default): string { + return (string)$default; + } + + /** + * @param bool $default + * @return string + * @experimental 31.0.0 + */ + private function convertFromBool(bool $default): string { + return ($default) ? '1' : '0'; + } + + /** + * @param array $default + * @return string + * @experimental 31.0.0 + */ + private function convertFromArray(array $default): string { + return json_encode($default); + } + + /** + * @inheritDoc + * + * @return string|null NULL if no default is set + * @experimental 31.0.0 + */ + public function getDefault(): ?string { + return $this->default; + } + + /** + * @inheritDoc + * + * @return string + * @experimental 31.0.0 + */ + public function getDefinition(): string { + return $this->definition; + } + + /** + * @inheritDoc + * + * @see IAppConfig for details on lazy config values + * @return bool TRUE if config value is lazy + * @experimental 31.0.0 + */ + public function isLazy(): bool { + return $this->lazy; + } + + /** + * @inheritDoc + * + * @see IAppConfig for details on sensitive config values + * @return int bitflag about the config value + * @experimental 31.0.0 + */ + public function getFlags(): int { + return $this->flags; + } + + /** + * @param int $flag + * + * @return bool TRUE is config value bitflag contains $flag + * @experimental 31.0.0 + */ + public function isFlagged(int $flag): bool { + return (($flag & $this->getFlags()) === $flag); + } + + /** + * @inheritDoc + * + * @return bool TRUE if config si deprecated + * @experimental 31.0.0 + */ + public function isDeprecated(): bool { + return $this->deprecated; + } +} diff --git a/lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php b/lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php new file mode 100644 index 00000000000..a6490675c8a --- /dev/null +++ b/lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php @@ -0,0 +1,30 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace NCU\Config\Lexicon; + +/** + * Strictness regarding using not-listed config keys + * + * - **ConfigLexiconStrictness::IGNORE** - fully ignore + * - **ConfigLexiconStrictness::NOTICE** - ignore and report + * - **ConfigLexiconStrictness::WARNING** - silently block (returns $default) and report + * - **ConfigLexiconStrictness::EXCEPTION** - block (throws exception) and report + * + * @experimental 31.0.0 + */ +enum ConfigLexiconStrictness { + /** @experimental 31.0.0 */ + case IGNORE; // fully ignore + /** @experimental 31.0.0 */ + case NOTICE; // ignore and report + /** @experimental 31.0.0 */ + case WARNING; // silently block (returns $default) and report + /** @experimental 31.0.0 */ + case EXCEPTION; // block (throws exception) and report +} diff --git a/lib/unstable/Config/Lexicon/IConfigLexicon.php b/lib/unstable/Config/Lexicon/IConfigLexicon.php new file mode 100644 index 00000000000..3fedb5f1f08 --- /dev/null +++ b/lib/unstable/Config/Lexicon/IConfigLexicon.php @@ -0,0 +1,44 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace NCU\Config\Lexicon; + +/** + * This interface needs to be implemented if you want to define a config lexicon for your application + * The config lexicon is used to avoid conflicts and problems when storing/retrieving config values + * + * @experimental 31.0.0 + */ +interface IConfigLexicon { + + /** + * Define the expected behavior when using config + * keys not set within your application config lexicon. + * + * @see ConfigLexiconStrictness + * @return ConfigLexiconStrictness + * @experimental 31.0.0 + */ + public function getStrictness(): ConfigLexiconStrictness; + + /** + * define the list of entries of your application config lexicon, related to AppConfig. + * + * @return ConfigLexiconEntry[] + * @experimental 31.0.0 + */ + public function getAppConfigs(): array; + + /** + * define the list of entries of your application config lexicon, related to UserPreferences. + * + * @return ConfigLexiconEntry[] + * @experimental 31.0.0 + */ + public function getUserConfigs(): array; +} diff --git a/lib/unstable/Config/ValueType.php b/lib/unstable/Config/ValueType.php index 4f6c4181a9c..b1181674953 100644 --- a/lib/unstable/Config/ValueType.php +++ b/lib/unstable/Config/ValueType.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace NCU\Config; use NCU\Config\Exceptions\IncorrectTypeException; +use OCP\IAppConfig; use UnhandledMatchError; /** @@ -89,4 +90,28 @@ enum ValueType: int { throw new IncorrectTypeException('unknown type definition ' . $this->value); } } + + /** + * get corresponding AppConfig flag value + * + * @return int + * @throws IncorrectTypeException + * + * @experimental 31.0.0 + */ + public function toAppConfigFlag(): int { + try { + return match ($this) { + self::MIXED => IAppConfig::VALUE_MIXED, + self::STRING => IAppConfig::VALUE_STRING, + self::INT => IAppConfig::VALUE_INT, + self::FLOAT => IAppConfig::VALUE_FLOAT, + self::BOOL => IAppConfig::VALUE_BOOL, + self::ARRAY => IAppConfig::VALUE_ARRAY, + }; + } catch (UnhandledMatchError) { + throw new IncorrectTypeException('unknown type definition ' . $this->value); + } + } + } |