aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorMaxence Lange <maxence@artificial-owl.com>2024-12-16 14:26:01 -0100
committerGitHub <noreply@github.com>2024-12-16 14:26:01 -0100
commitd399d47d9be520f71b737dd61f64d2a2ee940c5c (patch)
treeac029831479bcd98982fd16a62c36d0ed0f4914e /lib
parentf9ee3505a073c84e86c3d76485096883e2fff7e6 (diff)
parent815991741d2001f7610047ef9627283037c1beec (diff)
downloadnextcloud-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.php3
-rw-r--r--lib/composer/composer/autoload_static.php3
-rw-r--r--lib/private/AppConfig.php122
-rw-r--r--lib/private/AppFramework/Bootstrap/RegistrationContext.php34
-rw-r--r--lib/private/Config/UserConfig.php107
-rw-r--r--lib/public/AppFramework/Bootstrap/IRegistrationContext.php11
-rw-r--r--lib/public/IAppConfig.php3
-rw-r--r--lib/unstable/Config/Lexicon/ConfigLexiconEntry.php189
-rw-r--r--lib/unstable/Config/Lexicon/ConfigLexiconStrictness.php30
-rw-r--r--lib/unstable/Config/Lexicon/IConfigLexicon.php44
-rw-r--r--lib/unstable/Config/ValueType.php25
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);
+ }
+ }
+
}