toolkit_gts/plugin.rs
1//! Base GTS Type for toolkit plugin instances.
2//!
3//! Every toolkit plugin registers a well-known GTS Instance of this Type Schema
4//! in `types-registry` so consumers can discover and resolve plugins at runtime.
5//! Gear-specific plugin specs (e.g. `AuthZResolverPluginSpecV1`,
6//! `TenantResolverPluginSpecV1`) derive from this base type via the upstream
7//! `#[gts_macros::struct_to_gts_schema(base = PluginV1, ...)]` pattern.
8//!
9//! ## Renamed from `BaseToolkitPluginV1`
10//!
11//! This struct was previously named `BaseToolkitPluginV1` (and briefly
12//! `ToolkitPluginV1`) and lived in `libs/toolkit/src/gts/plugin.rs`. It now
13//! lives here, in the `toolkit-gts` crate, alongside other platform-wide
14//! GTS base types. A deprecated type alias
15//! `toolkit::gts::BaseToolkitPluginV1<P>` is retained in the toolkit crate for
16//! backward compatibility of external (published-crate) consumers.
17
18use crate::gts_type_schema;
19use gts::GtsInstanceId;
20
21/// Base Type Schema for all toolkit plugin instances.
22///
23/// Plugins of any kind (resolvers, gateways, policy engines, etc.) register a
24/// well-known GTS Instance of this type in `types-registry`. The `properties`
25/// generic holds the plugin-kind-specific spec type (derived from this base
26/// via GTS type chaining).
27///
28/// GTS Type Identifier: `gts.cf.toolkit.plugins.plugin.v1~`
29#[derive(Debug)]
30#[gts_type_schema(
31 dir_path = "schemas",
32 type_id = "gts.cf.toolkit.plugins.plugin.v1~",
33 description = "Base toolkit plugin schema",
34 properties = "id,vendor,priority,properties",
35 base = true
36)]
37pub struct PluginV1<P: gts::GtsSchema> {
38 /// Full GTS Instance Identifier for this plugin instance.
39 pub id: GtsInstanceId,
40 /// Vendor name, used for plugin selection when multiple of the same kind
41 /// are registered.
42 pub vendor: String,
43 /// Selection priority — lower = higher priority.
44 pub priority: i16,
45 /// Plugin-kind-specific spec (derived type's properties).
46 pub properties: P,
47}
48
49impl<P: gts::GtsSchema + gts::GtsSerialize + Default> PluginV1<P> {
50 /// Assembles a `PluginV1<P>` from runtime config and returns the pair
51 /// `(instance_id, json_payload)` ready for scoped-client registration
52 /// and `TypesRegistryClient::register(...)`.
53 ///
54 /// `P` is constructed internally via `P::default()`. Derived unit-struct
55 /// plugin specs declared through `#[toolkit_gts::gts_type_schema(base = PluginV1, ...)]`
56 /// must `#[derive(Default)]` so the caller only specifies the type
57 /// once — in the turbofish.
58 ///
59 /// Typical usage in a plugin gear's `init()`:
60 ///
61 /// ```ignore
62 /// let (instance_id, payload) = PluginV1::<MyPluginSpecV1>::build_registration(
63 /// "<vendor>.<package>.<plugin_name>.v1", // instance segment
64 /// cfg.vendor, // from YAML
65 /// cfg.priority, // from YAML
66 /// )?;
67 ///
68 /// // Publish to types-registry:
69 /// let results = registry.register(vec![payload]).await?;
70 /// RegisterResult::ensure_all_ok(&results)?;
71 ///
72 /// // Register scoped client under the same instance id:
73 /// ctx.client_hub().register_scoped::<dyn MyPluginClient>(
74 /// ClientScope::gts_id(&instance_id),
75 /// api,
76 /// );
77 /// ```
78 ///
79 /// The final instance id is `P::SCHEMA_ID + instance_segment` (e.g.
80 /// `gts.cf.toolkit.plugins.plugin.v1~cf.core.authn_resolver.plugin.v1~cf.builtin.static_authn_resolver.plugin.v1`).
81 /// Registration via `types-registry-sdk` stays in the caller — this
82 /// helper only builds the payload.
83 ///
84 /// For plugin specs that carry *real* data in `P` (rare — most specs
85 /// are unit-struct markers), bypass this helper and construct
86 /// `PluginV1 { id, vendor, priority, properties }` manually.
87 ///
88 /// # Errors
89 ///
90 /// Returns a `serde_json::Error` if serialisation fails (should not
91 /// happen for well-formed derived specs produced by
92 /// `#[struct_to_gts_schema(base = PluginV1, ...)]`).
93 pub fn build_registration(
94 instance_segment: &str,
95 vendor: impl Into<String>,
96 priority: i16,
97 ) -> serde_json::Result<(GtsInstanceId, serde_json::Value)> {
98 let id = GtsInstanceId::new(<P as gts::GtsSchema>::TYPE_ID, instance_segment);
99 let instance = Self {
100 id: id.clone(),
101 vendor: vendor.into(),
102 priority,
103 properties: P::default(),
104 };
105 let payload = serde_json::to_value(&instance)?;
106 Ok((id, payload))
107 }
108}