Skip to main content

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}