Skip to content

Commit e2e345d

Browse files
committed
feat(config): implement config lexicon
Signed-off-by: Maxence Lange <[email protected]>
1 parent a219133 commit e2e345d

File tree

11 files changed

+553
-0
lines changed

11 files changed

+553
-0
lines changed

lib/composer/composer/autoload_classmap.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@
219219
'OCP\\Comments\\MessageTooLongException' => $baseDir . '/lib/public/Comments/MessageTooLongException.php',
220220
'OCP\\Comments\\NotFoundException' => $baseDir . '/lib/public/Comments/NotFoundException.php',
221221
'OCP\\Common\\Exception\\NotFoundException' => $baseDir . '/lib/public/Common/Exception/NotFoundException.php',
222+
'OCP\\ConfigLexicon\\ConfigLexiconEntry' => $baseDir . '/lib/public/ConfigLexicon/ConfigLexiconEntry.php',
223+
'OCP\\ConfigLexicon\\ConfigLexiconStrictness' => $baseDir . '/lib/public/ConfigLexicon/ConfigLexiconStrictness.php',
224+
'OCP\\ConfigLexicon\\IConfigLexicon' => $baseDir . '/lib/public/ConfigLexicon/IConfigLexicon.php',
225+
'OCP\\ConfigLexicon\\IConfigLexiconEntry' => $baseDir . '/lib/public/ConfigLexicon/IConfigLexiconEntry.php',
226+
'OCP\\ConfigLexicon\\ValueType' => $baseDir . '/lib/public/ConfigLexicon/ValueType.php',
222227
'OCP\\Config\\BeforePreferenceDeletedEvent' => $baseDir . '/lib/public/Config/BeforePreferenceDeletedEvent.php',
223228
'OCP\\Config\\BeforePreferenceSetEvent' => $baseDir . '/lib/public/Config/BeforePreferenceSetEvent.php',
224229
'OCP\\Console\\ConsoleEvent' => $baseDir . '/lib/public/Console/ConsoleEvent.php',

lib/composer/composer/autoload_static.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,11 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
252252
'OCP\\Comments\\MessageTooLongException' => __DIR__ . '/../../..' . '/lib/public/Comments/MessageTooLongException.php',
253253
'OCP\\Comments\\NotFoundException' => __DIR__ . '/../../..' . '/lib/public/Comments/NotFoundException.php',
254254
'OCP\\Common\\Exception\\NotFoundException' => __DIR__ . '/../../..' . '/lib/public/Common/Exception/NotFoundException.php',
255+
'OCP\\ConfigLexicon\\ConfigLexiconEntry' => __DIR__ . '/../../..' . '/lib/public/ConfigLexicon/ConfigLexiconEntry.php',
256+
'OCP\\ConfigLexicon\\ConfigLexiconStrictness' => __DIR__ . '/../../..' . '/lib/public/ConfigLexicon/ConfigLexiconStrictness.php',
257+
'OCP\\ConfigLexicon\\IConfigLexicon' => __DIR__ . '/../../..' . '/lib/public/ConfigLexicon/IConfigLexicon.php',
258+
'OCP\\ConfigLexicon\\IConfigLexiconEntry' => __DIR__ . '/../../..' . '/lib/public/ConfigLexicon/IConfigLexiconEntry.php',
259+
'OCP\\ConfigLexicon\\ValueType' => __DIR__ . '/../../..' . '/lib/public/ConfigLexicon/ValueType.php',
255260
'OCP\\Config\\BeforePreferenceDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/Config/BeforePreferenceDeletedEvent.php',
256261
'OCP\\Config\\BeforePreferenceSetEvent' => __DIR__ . '/../../..' . '/lib/public/Config/BeforePreferenceSetEvent.php',
257262
'OCP\\Console\\ConsoleEvent' => __DIR__ . '/../../..' . '/lib/public/Console/ConsoleEvent.php',

lib/private/AppConfig.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111

1212
use InvalidArgumentException;
1313
use JsonException;
14+
use OC\AppFramework\Bootstrap\Coordinator;
15+
use OCP\ConfigLexicon\ConfigLexiconEntry;
16+
use OCP\ConfigLexicon\ConfigLexiconStrictness;
17+
use OCP\ConfigLexicon\IConfigLexiconEntry;
18+
use OCP\ConfigLexicon\ValueType;
1419
use OCP\DB\Exception as DBException;
1520
use OCP\DB\QueryBuilder\IQueryBuilder;
1621
use OCP\Exceptions\AppConfigIncorrectTypeException;
@@ -55,6 +60,8 @@ class AppConfig implements IAppConfig {
5560
private array $valueTypes = []; // type for all config values
5661
private bool $fastLoaded = false;
5762
private bool $lazyLoaded = false;
63+
/** @var array<array-key, array{entries: array<array-key, IConfigLexiconEntry>, strict: bool}> ['app_id' => ['strict' => bool, 'entries' => ['config_key' => IConfigLexiconEntry[]]] */
64+
private array $configLexiconDetails = [];
5865

5966
/**
6067
* $migrationCompleted is only needed to manage the previous structure
@@ -430,6 +437,10 @@ private function getTypedValue(
430437
int $type,
431438
): string {
432439
$this->assertParams($app, $key, valueType: $type);
440+
if (!$this->compareRegisteredConfigValues($app, $key, $lazy, $type, $default)) {
441+
return $default; // returns default if strictness of lexicon is set to WARNING (block and report)
442+
}
443+
433444
$this->loadConfig($lazy);
434445

435446
/**
@@ -721,6 +732,9 @@ private function setTypedValue(
721732
int $type,
722733
): bool {
723734
$this->assertParams($app, $key);
735+
if (!$this->compareRegisteredConfigValues($app, $key, $lazy, $type)) {
736+
return false; // returns false as database is not updated
737+
}
724738
$this->loadConfig($lazy);
725739

726740
$sensitive = $this->isTyped(self::VALUE_SENSITIVE, $type);
@@ -1538,4 +1552,109 @@ private function getSensitiveKeys(string $app): array {
15381552
public function clearCachedConfig(): void {
15391553
$this->clearCache();
15401554
}
1555+
1556+
/**
1557+
* verify and compare current use of config values with defined lexicon
1558+
*
1559+
* @throws AppConfigUnknownKeyException
1560+
* @throws AppConfigTypeConflictException
1561+
*/
1562+
private function compareRegisteredConfigValues(
1563+
string $app,
1564+
string $key,
1565+
bool &$lazy,
1566+
int &$type,
1567+
string &$default = '',
1568+
): bool {
1569+
if (in_array($key,
1570+
[
1571+
'enabled',
1572+
'installed_version',
1573+
'types',
1574+
])) {
1575+
return false;
1576+
}
1577+
$configDetails = $this->getConfigDetailsFromLexicon($app);
1578+
if (!array_key_exists($key, $configDetails['entries'])) {
1579+
return $this->applyLexiconStrictness($app, $key, $configDetails['strictness']);
1580+
}
1581+
1582+
/** @var ConfigLexiconEntry $configValue */
1583+
$configValue = $configDetails['entries'][$key];
1584+
$type &= ~self::VALUE_SENSITIVE;
1585+
1586+
if ($type === self::VALUE_MIXED) {
1587+
$type = $configValue->getValueType()->value; // we overwrite if value was requested as mixed
1588+
} else if ($configValue->getValueType()->value !== $type) {
1589+
throw new AppConfigTypeConflictException('The key ' . $app . '/' . $key . ' is typed incorrectly in relation to the config lexicon');
1590+
}
1591+
1592+
$lazy = $configValue->isLazy();
1593+
$default = $configValue->getDefault() ?? $default; // default from Lexicon got priority
1594+
if ($configValue->isSensitive()) {
1595+
$type |= self::VALUE_SENSITIVE;
1596+
}
1597+
if ($configValue->isDeprecated()) {
1598+
$this->logger->notice('config value ' . $app . '/' . $key . ' is set as deprecated.');
1599+
}
1600+
1601+
return true;
1602+
}
1603+
1604+
/**
1605+
* @param string $app
1606+
* @param string $key
1607+
* @param ConfigLexiconStrictness $strictness
1608+
*
1609+
* @return bool TRUE if conflict can be fully ignored
1610+
* @throws AppConfigUnknownKeyException
1611+
*/
1612+
private function applyLexiconStrictness(
1613+
string $app,
1614+
string $key,
1615+
?ConfigLexiconStrictness $strictness
1616+
): bool {
1617+
if ($strictness === null) {
1618+
return true;
1619+
}
1620+
1621+
$line = 'The key ' . $app . '/' . $key . ' is not defined in the config lexicon';
1622+
switch($strictness) {
1623+
case ConfigLexiconStrictness::IGNORE:
1624+
return true;
1625+
case ConfigLexiconStrictness::NOTICE:
1626+
$this->logger->notice($line);
1627+
return true;
1628+
case ConfigLexiconStrictness::WARNING:
1629+
$this->logger->warning($line);
1630+
return false;
1631+
}
1632+
1633+
throw new AppConfigUnknownKeyException($line);
1634+
}
1635+
1636+
/**
1637+
* extract details from registered $appId's config lexicon
1638+
*
1639+
* @param string $appId
1640+
*
1641+
* @return array{entries: array<array-key, IConfigLexiconEntry>, strict: bool}
1642+
*/
1643+
private function getConfigDetailsFromLexicon(string $appId): array {
1644+
if (!array_key_exists($appId, $this->configLexiconDetails)) {
1645+
$entries = [];
1646+
$bootstrapCoordinator = \OCP\Server::get(Coordinator::class);
1647+
$configLexicon = $bootstrapCoordinator->getRegistrationContext()?->getConfigLexicon($appId);
1648+
foreach ($configLexicon?->getAppConfigs() ?? [] as $configEntry) {
1649+
$entries[$configEntry->getKey()] = $configEntry;
1650+
}
1651+
1652+
$this->configLexiconDetails[$appId] = [
1653+
'entries' => $entries,
1654+
'strictness' => $configLexicon?->getStrictness() ?? ConfigLexiconStrictness::IGNORE
1655+
];
1656+
}
1657+
1658+
return $this->configLexiconDetails[$appId];
1659+
}
15411660
}

lib/private/AppFramework/Bootstrap/RegistrationContext.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use OCP\Calendar\Room\IBackend as IRoomBackend;
2222
use OCP\Capabilities\ICapability;
2323
use OCP\Collaboration\Reference\IReferenceProvider;
24+
use OCP\ConfigLexicon\IConfigLexicon;
2425
use OCP\Dashboard\IManager;
2526
use OCP\Dashboard\IWidget;
2627
use OCP\EventDispatcher\IEventDispatcher;
@@ -141,6 +142,9 @@ class RegistrationContext {
141142
/** @var ServiceRegistration<IDeclarativeSettingsForm>[] */
142143
private array $declarativeSettings = [];
143144

145+
/** @var array<array-key, string> */
146+
private array $configLexiconClasses = [];
147+
144148
/** @var ServiceRegistration<ITeamResourceProvider>[] */
145149
private array $teamResourceProviders = [];
146150

@@ -422,6 +426,13 @@ public function registerMailProvider(string $class): void {
422426
$class
423427
);
424428
}
429+
430+
public function registerConfigLexicon(string $configLexiconClass): void {
431+
$this->context->registerConfigLexicon(
432+
$this->appId,
433+
$configLexiconClass
434+
);
435+
}
425436
};
426437
}
427438

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

635+
/**
636+
* @psalm-param class-string<IConfigLexicon> $configLexiconClass
637+
*/
638+
public function registerConfigLexicon(string $appId, string $configLexiconClass): void {
639+
$this->configLexiconClasses[$appId] = $configLexiconClass;
640+
}
641+
624642
/**
625643
* @param App[] $apps
626644
*/
@@ -972,4 +990,20 @@ public function getTaskProcessingTaskTypes(): array {
972990
public function getMailProviders(): array {
973991
return $this->mailProviders;
974992
}
993+
994+
/**
995+
* returns IConfigLexicon registered by the app.
996+
* null if none registered.
997+
*
998+
* @param string $appId
999+
*
1000+
* @return IConfigLexicon|null
1001+
*/
1002+
public function getConfigLexicon(string $appId): ?IConfigLexicon {
1003+
if (!array_key_exists($appId, $this->configLexiconClasses)) {
1004+
return null;
1005+
}
1006+
1007+
return \OCP\Server::get($this->configLexiconClasses[$appId]);
1008+
}
9751009
}

lib/public/AppFramework/Bootstrap/IRegistrationContext.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,4 +423,14 @@ public function registerTaskProcessingTaskType(string $taskProcessingTaskTypeCla
423423
*/
424424
public function registerMailProvider(string $class): void;
425425

426+
427+
/**
428+
* Register an implementation of \OCP\ConfigLexicon\IConfigLexicon that
429+
* will handle the implementation of config lexicon
430+
*
431+
* @param string $configLexiconClass
432+
* @psalm-param class-string<\OCP\ConfigLexicon\IConfigLexicon> $configLexiconClass
433+
* @since 31.0.0
434+
*/
435+
public function registerConfigLexicon(string $configLexiconClass): void;
426436
}

0 commit comments

Comments
 (0)