Skip to content

Commit

Permalink
feat(config): implement config lexicon
Browse files Browse the repository at this point in the history
Signed-off-by: Maxence Lange <[email protected]>
  • Loading branch information
ArtificialOwl committed Oct 1, 2024
1 parent a219133 commit e2e345d
Show file tree
Hide file tree
Showing 11 changed files with 553 additions and 0 deletions.
5 changes: 5 additions & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@
'OCP\\Comments\\MessageTooLongException' => $baseDir . '/lib/public/Comments/MessageTooLongException.php',
'OCP\\Comments\\NotFoundException' => $baseDir . '/lib/public/Comments/NotFoundException.php',
'OCP\\Common\\Exception\\NotFoundException' => $baseDir . '/lib/public/Common/Exception/NotFoundException.php',
'OCP\\ConfigLexicon\\ConfigLexiconEntry' => $baseDir . '/lib/public/ConfigLexicon/ConfigLexiconEntry.php',
'OCP\\ConfigLexicon\\ConfigLexiconStrictness' => $baseDir . '/lib/public/ConfigLexicon/ConfigLexiconStrictness.php',
'OCP\\ConfigLexicon\\IConfigLexicon' => $baseDir . '/lib/public/ConfigLexicon/IConfigLexicon.php',
'OCP\\ConfigLexicon\\IConfigLexiconEntry' => $baseDir . '/lib/public/ConfigLexicon/IConfigLexiconEntry.php',
'OCP\\ConfigLexicon\\ValueType' => $baseDir . '/lib/public/ConfigLexicon/ValueType.php',
'OCP\\Config\\BeforePreferenceDeletedEvent' => $baseDir . '/lib/public/Config/BeforePreferenceDeletedEvent.php',
'OCP\\Config\\BeforePreferenceSetEvent' => $baseDir . '/lib/public/Config/BeforePreferenceSetEvent.php',
'OCP\\Console\\ConsoleEvent' => $baseDir . '/lib/public/Console/ConsoleEvent.php',
Expand Down
5 changes: 5 additions & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,11 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Comments\\MessageTooLongException' => __DIR__ . '/../../..' . '/lib/public/Comments/MessageTooLongException.php',
'OCP\\Comments\\NotFoundException' => __DIR__ . '/../../..' . '/lib/public/Comments/NotFoundException.php',
'OCP\\Common\\Exception\\NotFoundException' => __DIR__ . '/../../..' . '/lib/public/Common/Exception/NotFoundException.php',
'OCP\\ConfigLexicon\\ConfigLexiconEntry' => __DIR__ . '/../../..' . '/lib/public/ConfigLexicon/ConfigLexiconEntry.php',
'OCP\\ConfigLexicon\\ConfigLexiconStrictness' => __DIR__ . '/../../..' . '/lib/public/ConfigLexicon/ConfigLexiconStrictness.php',
'OCP\\ConfigLexicon\\IConfigLexicon' => __DIR__ . '/../../..' . '/lib/public/ConfigLexicon/IConfigLexicon.php',
'OCP\\ConfigLexicon\\IConfigLexiconEntry' => __DIR__ . '/../../..' . '/lib/public/ConfigLexicon/IConfigLexiconEntry.php',
'OCP\\ConfigLexicon\\ValueType' => __DIR__ . '/../../..' . '/lib/public/ConfigLexicon/ValueType.php',
'OCP\\Config\\BeforePreferenceDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/Config/BeforePreferenceDeletedEvent.php',
'OCP\\Config\\BeforePreferenceSetEvent' => __DIR__ . '/../../..' . '/lib/public/Config/BeforePreferenceSetEvent.php',
'OCP\\Console\\ConsoleEvent' => __DIR__ . '/../../..' . '/lib/public/Console/ConsoleEvent.php',
Expand Down
119 changes: 119 additions & 0 deletions lib/private/AppConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@

use InvalidArgumentException;
use JsonException;
use OC\AppFramework\Bootstrap\Coordinator;
use OCP\ConfigLexicon\ConfigLexiconEntry;
use OCP\ConfigLexicon\ConfigLexiconStrictness;
use OCP\ConfigLexicon\IConfigLexiconEntry;
use OCP\ConfigLexicon\ValueType;
use OCP\DB\Exception as DBException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Exceptions\AppConfigIncorrectTypeException;
Expand Down Expand Up @@ -55,6 +60,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, IConfigLexiconEntry>, strict: bool}> ['app_id' => ['strict' => bool, 'entries' => ['config_key' => IConfigLexiconEntry[]]] */
private array $configLexiconDetails = [];

/**
* $migrationCompleted is only needed to manage the previous structure
Expand Down Expand Up @@ -430,6 +437,10 @@ private function getTypedValue(
int $type,
): string {
$this->assertParams($app, $key, valueType: $type);
if (!$this->compareRegisteredConfigValues($app, $key, $lazy, $type, $default)) {
return $default; // returns default if strictness of lexicon is set to WARNING (block and report)
}

$this->loadConfig($lazy);

/**
Expand Down Expand Up @@ -721,6 +732,9 @@ private function setTypedValue(
int $type,
): bool {
$this->assertParams($app, $key);
if (!$this->compareRegisteredConfigValues($app, $key, $lazy, $type)) {
return false; // returns false as database is not updated
}
$this->loadConfig($lazy);

$sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
Expand Down Expand Up @@ -1538,4 +1552,109 @@ private function getSensitiveKeys(string $app): array {
public function clearCachedConfig(): void {
$this->clearCache();
}

/**
* verify and compare current use of config values with defined lexicon
*
* @throws AppConfigUnknownKeyException
* @throws AppConfigTypeConflictException
*/
private function compareRegisteredConfigValues(
string $app,
string $key,
bool &$lazy,
int &$type,
string &$default = '',
): bool {
if (in_array($key,
[
'enabled',
'installed_version',
'types',
])) {
return false;
}
$configDetails = $this->getConfigDetailsFromLexicon($app);
if (!array_key_exists($key, $configDetails['entries'])) {
return $this->applyLexiconStrictness($app, $key, $configDetails['strictness']);

Check failure on line 1579 in lib/private/AppConfig.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidArrayOffset

lib/private/AppConfig.php:1579:53: InvalidArrayOffset: Cannot access value on variable $configDetails using offset value of 'strictness', expecting 'entries' or 'strict' (see https://psalm.dev/115)
}

/** @var ConfigLexiconEntry $configValue */
$configValue = $configDetails['entries'][$key];
$type &= ~self::VALUE_SENSITIVE;

if ($type === self::VALUE_MIXED) {
$type = $configValue->getValueType()->value; // we overwrite if value was requested as mixed
} else if ($configValue->getValueType()->value !== $type) {
throw new AppConfigTypeConflictException('The 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->isSensitive()) {
$type |= self::VALUE_SENSITIVE;
}
if ($configValue->isDeprecated()) {
$this->logger->notice('config value ' . $app . '/' . $key . ' is set as deprecated.');
}

return true;
}

/**
* @param string $app
* @param string $key
* @param ConfigLexiconStrictness $strictness
*
* @return bool TRUE if conflict can be fully ignored
* @throws AppConfigUnknownKeyException
*/
private function applyLexiconStrictness(
string $app,
string $key,
?ConfigLexiconStrictness $strictness
): bool {
if ($strictness === null) {
return true;
}

$line = 'The key ' . $app . '/' . $key . ' is not defined in the config lexicon';
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, IConfigLexiconEntry>, strict: bool}

Check failure on line 1641 in lib/private/AppConfig.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidReturnType

lib/private/AppConfig.php:1641:13: InvalidReturnType: The declared return type 'array{entries: array<array-key, OCP\ConfigLexicon\IConfigLexiconEntry>, strict: bool}' for OC\AppConfig::getConfigDetailsFromLexicon is incorrect, got 'array{entries: array<array-key, OCP\ConfigLexicon\IConfigLexiconEntry>, strict?: bool, strictness?: OCP\ConfigLexicon\ConfigLexiconStrictness}' which is different due to additional array shape fields (strict, strictness) (see https://psalm.dev/011)
*/
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] = [

Check failure on line 1652 in lib/private/AppConfig.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidPropertyAssignmentValue

lib/private/AppConfig.php:1652:4: InvalidPropertyAssignmentValue: $this->configLexiconDetails with declared type 'array<array-key, array{entries: array<array-key, OCP\ConfigLexicon\IConfigLexiconEntry>, strict: bool}>' cannot be assigned type 'non-empty-array<array-key, array{entries: array<array-key, OCP\ConfigLexicon\IConfigLexiconEntry>, strict?: bool, strictness?: OCP\ConfigLexicon\ConfigLexiconStrictness|enum(OCP\ConfigLexicon\ConfigLexiconStrictness::IGNORE)}>' (see https://psalm.dev/145)
'entries' => $entries,
'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE
];
}

return $this->configLexiconDetails[$appId];

Check failure on line 1658 in lib/private/AppConfig.php

View workflow job for this annotation

GitHub Actions / static-code-analysis

InvalidReturnStatement

lib/private/AppConfig.php:1658:10: InvalidReturnStatement: The inferred type 'array{entries: array<array-key, OCP\ConfigLexicon\IConfigLexiconEntry>, strict?: bool, strictness?: OCP\ConfigLexicon\ConfigLexiconStrictness}' does not match the declared return type 'array{entries: array<array-key, OCP\ConfigLexicon\IConfigLexiconEntry>, strict: bool}' for OC\AppConfig::getConfigDetailsFromLexicon due to additional array shape fields (strict, strictness) (see https://psalm.dev/128)
}
}
34 changes: 34 additions & 0 deletions lib/private/AppFramework/Bootstrap/RegistrationContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use OCP\Calendar\Room\IBackend as IRoomBackend;
use OCP\Capabilities\ICapability;
use OCP\Collaboration\Reference\IReferenceProvider;
use OCP\ConfigLexicon\IConfigLexicon;
use OCP\Dashboard\IManager;
use OCP\Dashboard\IWidget;
use OCP\EventDispatcher\IEventDispatcher;
Expand Down Expand Up @@ -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 = [];

Expand Down Expand Up @@ -422,6 +426,13 @@ public function registerMailProvider(string $class): void {
$class
);
}

public function registerConfigLexicon(string $configLexiconClass): void {
$this->context->registerConfigLexicon(
$this->appId,
$configLexiconClass
);
}
};
}

Expand Down Expand Up @@ -621,6 +632,13 @@ public function registerMailProvider(string $appId, string $class): void {
$this->mailProviders[] = new ServiceRegistration($appId, $class);
}

/**
* @psalm-param class-string<IConfigLexicon> $configLexiconClass
*/
public function registerConfigLexicon(string $appId, string $configLexiconClass): void {
$this->configLexiconClasses[$appId] = $configLexiconClass;
}

/**
* @param App[] $apps
*/
Expand Down Expand Up @@ -972,4 +990,20 @@ public function getTaskProcessingTaskTypes(): array {
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]);
}
}
10 changes: 10 additions & 0 deletions lib/public/AppFramework/Bootstrap/IRegistrationContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -423,4 +423,14 @@ public function registerTaskProcessingTaskType(string $taskProcessingTaskTypeCla
*/
public function registerMailProvider(string $class): void;


/**
* Register an implementation of \OCP\ConfigLexicon\IConfigLexicon that
* will handle the implementation of config lexicon
*
* @param string $configLexiconClass
* @psalm-param class-string<\OCP\ConfigLexicon\IConfigLexicon> $configLexiconClass
* @since 31.0.0
*/
public function registerConfigLexicon(string $configLexiconClass): void;
}
Loading

0 comments on commit e2e345d

Please sign in to comment.