-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathExtensionNamespace.php
More file actions
81 lines (74 loc) · 2.66 KB
/
ExtensionNamespace.php
File metadata and controls
81 lines (74 loc) · 2.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?php
declare(strict_types=1);
namespace Arcp\Extensions;
/**
* Validation for ARCP extension type/field names (RFC §21.1).
*
* Accepted forms:
* - `arcpx.<vendor-or-domain>.<name>.v<n>` (preferred).
* - Reverse-DNS prefix (`com.acme.workflow.v2`).
*
* The bare `x-` prefix is reserved for **transport-internal** experimental
* fields and MUST NOT appear as a long-lived envelope type. We accept it
* only on `extensions.<x-foo>` keys, never as message-type names.
*
* Core message-type prefixes (`session.`, `tool.`, `job.`, `stream.`,
* `human.`, `permission.`, `lease.`, `subscribe`, `unsubscribe`,
* `artifact.`, `event.emit`, `log`, `metric`, `trace.span`, `ping`,
* `pong`, `ack`, `nack`, `cancel`, `interrupt`, `resume`, `backpressure`,
* `checkpoint.`, `workflow.`, `agent.`) are reserved and rejected here.
*/
final class ExtensionNamespace
{
/** @var list<string> */
private const array CORE_PREFIXES = [
'session.', 'tool.', 'job.', 'stream.',
'human.', 'permission.', 'lease.',
'subscribe', 'unsubscribe', 'artifact.',
'event.emit', 'log', 'metric', 'trace.span',
'ping', 'pong', 'ack', 'nack',
'cancel', 'interrupt', 'resume', 'backpressure',
'checkpoint.', 'workflow.', 'agent.',
];
private function __construct()
{
}
/** True iff `$type` matches one of the core prefixes (RFC §21.3). */
public static function isCore(string $type): bool
{
foreach (self::CORE_PREFIXES as $prefix) {
if (str_ends_with($prefix, '.')) {
if (str_starts_with($type, $prefix)) {
return true;
}
} elseif ($type === $prefix) {
return true;
}
}
return false;
}
/** True iff `$type` is a syntactically valid extension type-name. */
public static function isValidExtension(string $type): bool
{
if (self::isCore($type)) {
return false;
}
// arcpx.<vendor>.<name>.v<n> e.g. arcpx.example.v1
// reverse-DNS prefix e.g. com.acme.workflow.v2
return preg_match('/^[a-z][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)+$/', $type) === 1;
}
/**
* Reject a name with a structured error. Throws
* {@see \Arcp\Errors\InvalidArgumentException} so the caller can lift
* it onto a `nack` envelope.
*/
public static function ensureValidExtension(string $type): void
{
if (!self::isValidExtension($type)) {
throw new \Arcp\Errors\InvalidArgumentException(
\sprintf('not a valid ARCP extension type: %s', $type),
['type' => $type],
);
}
}
}