Skip to main content

genja_core/inventory/
transform.rs

1use super::{Defaults, DerefTarget, Group, Host};
2use genja_core_derive::{DerefMacro, DerefMutMacro};
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5use std::fmt;
6use std::sync::Arc;
7
8/// A trait for implementing custom transformation logic on inventory entities.
9///
10/// The `Transform` trait provides a flexible mechanism to modify hosts, groups, and defaults
11/// in an inventory based on custom logic, external configuration, or runtime conditions.
12/// Implementations of this trait can be wrapped in a `TransformFunction` and applied to an
13/// inventory to dynamically alter entity properties without modifying the underlying data.
14///
15/// All methods in this trait have default implementations that return clones of the input
16/// entities unchanged. Implementors only need to override the methods for entity types they
17/// wish to transform.
18///
19/// # Thread Safety
20///
21/// Implementations must be `Send + Sync` to allow safe sharing across threads. The inventory
22/// system uses `Arc` internally to share transform functions, so all transform logic must be
23/// thread-safe.
24///
25/// # Transform Methods
26///
27/// The trait provides three transformation methods, one for each inventory entity type:
28///
29/// * `transform_host` - Transforms individual host configurations
30/// * `transform_group` - Transforms group configurations
31/// * `transform_defaults` - Transforms inventory-wide defaults
32///
33/// Each method receives a reference to the entity being transformed and optional configuration
34/// through `TransformFunctionOptions`. The methods should return a new instance of the entity
35/// with the desired modifications applied.
36///
37/// # When Transforms Are Applied
38///
39/// Transforms are applied lazily when accessing inventory entities:
40/// - When calling `Inventory::hosts()` and accessing individual hosts
41/// - When calling `Inventory::groups()` and accessing individual groups
42/// - When calling `Inventory::defaults()`
43/// - During host resolution via `Inventory::resolve_host()`
44///
45/// Results are cached for hosts and groups to improve performance on subsequent
46/// accesses. Resolved hosts and connection parameters are also cached by
47/// `Inventory`. Defaults are transformed lazily on each `Inventory::defaults()`
48/// call and are not stored in a dedicated cache.
49///
50/// # Transform Options
51///
52/// The optional `TransformFunctionOptions` parameter provides a way to pass configuration
53/// data to the transform function at runtime. This allows for flexible, data-driven
54/// transformations without hardcoding values in the transform implementation.
55///
56/// Options are stored as JSON values and can contain any structured data needed by the
57/// transform logic. Access the options using the `get()` method and JSON value accessors.
58///
59/// # Examples
60///
61/// ## Basic Host Transform
62///
63/// ```
64/// # use genja_core::inventory::{Transform, TransformFunction, TransformFunctionOptions};
65/// # use genja_core::inventory::{Host, Group, Defaults, BaseBuilderHost};
66/// struct PortTransform {
67///     default_port: u16,
68/// }
69///
70/// impl Transform for PortTransform {
71///     fn transform_host(&self, host: &Host, _options: Option<&TransformFunctionOptions>) -> Host {
72///         // Apply default port if host doesn't have one
73///         if host.port().is_none() {
74///             host.to_builder()
75///                 .port(self.default_port)
76///                 .build()
77///         } else {
78///             host.clone()
79///         }
80///     }
81/// }
82///
83/// let transform = TransformFunction::new_full(PortTransform { default_port: 2222 });
84/// ```
85///
86/// ## Transform Using Options
87///
88/// ```
89/// # use genja_core::inventory::{Transform, TransformFunction, TransformFunctionOptions};
90/// # use genja_core::inventory::{Host, Group, Defaults, BaseBuilderHost};
91/// struct PrefixTransform;
92///
93/// impl Transform for PrefixTransform {
94///     fn transform_host(&self, host: &Host, options: Option<&TransformFunctionOptions>) -> Host {
95///         // Get prefix from options
96///         let prefix = options
97///             .and_then(|opts| opts.get("hostname_prefix"))
98///             .and_then(|v| v.as_str())
99///             .unwrap_or("");
100///
101///         if !prefix.is_empty() {
102///             if let Some(hostname) = host.hostname() {
103///                 return host.to_builder()
104///                     .hostname(format!("{}{}", prefix, hostname))
105///                     .build();
106///             }
107///         }
108///         host.clone()
109///     }
110/// }
111///
112/// let transform = TransformFunction::new_full(PrefixTransform);
113/// let options = TransformFunctionOptions::new(
114///     serde_json::json!({"hostname_prefix": "prod-"})
115/// );
116/// ```
117///
118/// ## Multi-Entity Transform
119///
120/// ```
121/// # use genja_core::inventory::{Transform, TransformFunction, TransformFunctionOptions};
122/// # use genja_core::inventory::{Host, Group, Defaults, BaseBuilderHost};
123/// struct EnvironmentTransform {
124///     environment: String,
125/// }
126///
127/// impl Transform for EnvironmentTransform {
128///     fn transform_host(&self, host: &Host, _options: Option<&TransformFunctionOptions>) -> Host {
129///         // Add environment tag to hostname
130///         if let Some(hostname) = host.hostname() {
131///             host.to_builder()
132///                 .hostname(format!("{}.{}", hostname, self.environment))
133///                 .build()
134///         } else {
135///             host.clone()
136///         }
137///     }
138///
139///     fn transform_group(&self, group: &Group, _options: Option<&TransformFunctionOptions>) -> Group {
140///         // Apply environment-specific group settings
141///         if self.environment == "prod" {
142///             // Production groups might need different settings
143///             group.to_builder()
144///                 .port(443)
145///                 .build()
146///         } else {
147///             group.clone()
148///         }
149///     }
150///
151///     fn transform_defaults(&self, defaults: &Defaults, _options: Option<&TransformFunctionOptions>) -> Defaults {
152///         // Apply environment-specific defaults
153///         defaults.to_builder()
154///             .username(format!("{}-user", self.environment))
155///             .build()
156///     }
157/// }
158///
159/// let transform = TransformFunction::new_full(EnvironmentTransform {
160///     environment: "prod".to_string(),
161/// });
162/// ```
163///
164/// ## IP Address Mapping Transform
165///
166/// ```
167/// # use genja_core::inventory::{Transform, TransformFunction, TransformFunctionOptions};
168/// # use genja_core::inventory::{Host, Group, Defaults, BaseBuilderHost};
169/// struct IpMappingTransform;
170///
171/// impl Transform for IpMappingTransform {
172///     fn transform_host(&self, host: &Host, options: Option<&TransformFunctionOptions>) -> Host {
173///         // Get IP mapping from options
174///         let mapping = options
175///             .and_then(|opts| opts.get("ip_map"))
176///             .and_then(|v| v.as_object());
177///
178///         let Some(mapping) = mapping else {
179///             return host.clone();
180///         };
181///
182///         let mut builder = host.to_builder();
183///
184///         // Map hostname if it exists in the mapping
185///         if let Some(hostname) = host.hostname() {
186///             if let Some(mapped) = mapping.get(hostname).and_then(|v| v.as_str()) {
187///                 builder = builder.hostname(mapped);
188///             }
189///         }
190///
191///         builder.build()
192///     }
193/// }
194///
195/// let transform = TransformFunction::new_full(IpMappingTransform);
196/// let options = TransformFunctionOptions::new(serde_json::json!({
197///     "ip_map": {
198///         "10-0-0-1": "10.0.0.1",
199///         "10-0-0-2": "10.0.0.2"
200///     }
201/// }));
202/// ```
203pub trait Transform: Send + Sync {
204    /// Transforms a host entity.
205    ///
206    /// This method is called when a host is accessed through the inventory's host view
207    /// or during host resolution. The default implementation returns a clone of the
208    /// input host unchanged.
209    ///
210    /// # Parameters
211    ///
212    /// * `host` - A reference to the host being transformed
213    /// * `_options` - Optional configuration data for the transform
214    ///
215    /// # Returns
216    ///
217    /// Returns a new `Host` instance with the desired transformations applied.
218    fn transform_host(&self, host: &Host, _options: Option<&TransformFunctionOptions>) -> Host {
219        host.clone()
220    }
221
222    /// Transforms a group entity.
223    ///
224    /// This method is called when a group is accessed through the inventory's group view.
225    /// The default implementation returns a clone of the input group unchanged.
226    ///
227    /// # Parameters
228    ///
229    /// * `group` - A reference to the group being transformed
230    /// * `_options` - Optional configuration data for the transform
231    ///
232    /// # Returns
233    ///
234    /// Returns a new `Group` instance with the desired transformations applied.
235    fn transform_group(&self, group: &Group, _options: Option<&TransformFunctionOptions>) -> Group {
236        group.clone()
237    }
238
239    /// Transforms the inventory defaults.
240    ///
241    /// This method is called when defaults are accessed through `Inventory::defaults()`.
242    /// The default implementation returns a clone of the input defaults unchanged.
243    ///
244    /// # Parameters
245    ///
246    /// * `defaults` - A reference to the defaults being transformed
247    /// * `_options` - Optional configuration data for the transform
248    ///
249    /// # Returns
250    ///
251    /// Returns a new `Defaults` instance with the desired transformations applied.
252    fn transform_defaults(
253        &self,
254        defaults: &Defaults,
255        _options: Option<&TransformFunctionOptions>,
256    ) -> Defaults {
257        defaults.clone()
258    }
259}
260
261/// A thread-safe wrapper around a transform function that can modify inventory entities.
262///
263/// `TransformFunction` encapsulates custom logic for dynamically transforming hosts, groups,
264/// and defaults in an inventory. It provides a flexible mechanism to modify inventory data
265/// based on runtime conditions, external configuration, or custom business logic without
266/// altering the underlying inventory structure.
267///
268/// The wrapper uses `Arc` for thread-safe reference counting, enabling the transform function
269/// to be shared across multiple threads and cloned efficiently. All clones share the same
270/// underlying transform logic.
271///
272/// # Transform Types
273///
274/// There are two ways to create a `TransformFunction`:
275///
276/// 1. **Host-only transform** - Using `new()`, which accepts a closure that only transforms hosts.
277///    Groups and defaults pass through unchanged.
278///
279/// 2. **Full transform** - Using `new_full()`, which accepts a type implementing the `Transform`
280///    trait, allowing custom transformation of hosts, groups, and defaults.
281///
282/// # When Transforms Are Applied
283///
284/// Transforms are applied lazily when accessing inventory entities through:
285/// - `Inventory::hosts()` - Returns a `HostsView` that applies transforms on access
286/// - `Inventory::groups()` - Returns a `GroupsView` that applies transforms on access  
287/// - `Inventory::defaults()` - Returns transformed defaults
288/// - `Inventory::resolve_host()` - Applies transforms to the resolved host
289///
290/// Host and group transform results are cached to improve performance on
291/// subsequent accesses. Resolved hosts and connection parameters are also
292/// cached by `Inventory`. Defaults are transformed lazily on each
293/// `Inventory::defaults()` call and are not stored in a dedicated cache.
294///
295/// # Thread Safety
296///
297/// The `Clone` implementation creates a new reference to the same underlying transform
298/// function, not a deep copy. All clones share the same transform logic and can be safely
299/// used across threads.
300///
301/// # Examples
302///
303/// ## Host-only Transform
304///
305/// ```
306/// # use genja_core::inventory::{TransformFunction, Host, Inventory, Hosts, BaseBuilderHost};
307/// // Create a transform that modifies the port for all hosts
308/// let transform = TransformFunction::new(|host, _options| {
309///     host.to_builder()
310///         .port(2222)
311///         .build()
312/// });
313///
314/// let mut hosts = Hosts::new();
315/// hosts.add_host("router1", Host::builder().hostname("10.0.0.1").port(22).build());
316///
317/// let inventory = Inventory::builder()
318///     .hosts(hosts)
319///     .transform_function(transform)
320///     .build();
321///
322/// // Transform is applied when accessing the host
323/// let host = inventory.hosts().get("router1").unwrap();
324/// assert_eq!(host.port(), Some(2222));
325/// ```
326///
327/// ## Full Transform with Options
328///
329/// ```
330/// # use genja_core::inventory::{Transform, TransformFunction, TransformFunctionOptions};
331/// # use genja_core::inventory::{Host, Group, Defaults, Inventory, Hosts, BaseBuilderHost};
332/// struct CustomTransform;
333///
334/// impl Transform for CustomTransform {
335///     fn transform_host(&self, host: &Host, options: Option<&TransformFunctionOptions>) -> Host {
336///         // Access transform options if provided
337///         if let Some(opts) = options {
338///             if let Some(prefix) = opts.get("hostname_prefix").and_then(|v| v.as_str()) {
339///                 if let Some(hostname) = host.hostname() {
340///                     return host.to_builder()
341///                         .hostname(format!("{}{}", prefix, hostname))
342///                         .build();
343///                 }
344///             }
345///         }
346///         host.clone()
347///     }
348///
349///     fn transform_group(&self, group: &Group, _options: Option<&TransformFunctionOptions>) -> Group {
350///         // Custom group transformation logic
351///         group.clone()
352///     }
353/// }
354///
355/// let transform = TransformFunction::new_full(CustomTransform);
356/// let options = TransformFunctionOptions::new(
357///     serde_json::json!({"hostname_prefix": "prod-"})
358/// );
359///
360/// let mut hosts = Hosts::new();
361/// hosts.add_host("router1", Host::builder().hostname("router1").build());
362///
363/// let inventory = Inventory::builder()
364///     .hosts(hosts)
365///     .transform_function(transform)
366///     .transform_function_options(options)
367///     .build();
368///
369/// let host = inventory.hosts().get("router1").unwrap();
370/// assert_eq!(host.hostname(), Some("prod-router1"));
371/// ```
372///
373/// ## Cloning and Sharing
374///
375/// ```
376/// # use genja_core::inventory::{TransformFunction, Host};
377/// let transform = TransformFunction::new(|host: &Host, _| host.clone());
378///
379/// // Cloning creates a new reference to the same transform
380/// let transform_clone = transform.clone();
381///
382/// // Both can be used independently and share the same underlying logic
383/// ```
384#[derive(Clone)]
385pub struct TransformFunction(Arc<dyn Transform>);
386
387impl TransformFunction {
388    /// Creates a new transform function that only modifies hosts.
389    ///
390    /// This is a convenience constructor for the common case where you only need to transform
391    /// hosts. Groups and defaults will pass through unchanged. The provided closure receives
392    /// a reference to the host and optional transform options, and should return a new `Host`
393    /// instance with the desired modifications.
394    ///
395    /// # Type Parameters
396    ///
397    /// * `F` - A closure type that takes `(&Host, Option<&TransformFunctionOptions>)` and
398    ///   returns a `Host`. The closure must be `Send + Sync + 'static` to allow thread-safe
399    ///   sharing across the inventory.
400    ///
401    /// # Parameters
402    ///
403    /// * `func` - A closure that implements the host transformation logic. It receives:
404    ///   - `&Host` - A reference to the host being transformed
405    ///   - `Option<&TransformFunctionOptions>` - Optional configuration data for the transform
406    ///
407    /// # Returns
408    ///
409    /// Returns a new `TransformFunction` that applies the provided closure to hosts while
410    /// leaving groups and defaults unchanged.
411    ///
412    /// # Examples
413    ///
414    /// ```
415    /// # use genja_core::inventory::{TransformFunction, Host, BaseBuilderHost};
416    /// // Simple transform that sets a default port
417    /// let transform = TransformFunction::new(|host, _options| {
418    ///     if host.port().is_none() {
419    ///         host.to_builder().port(22).build()
420    ///     } else {
421    ///         host.clone()
422    ///     }
423    /// });
424    /// ```
425    ///
426    /// ```
427    /// # use genja_core::inventory::{TransformFunction, Host, BaseBuilderHost};
428    /// // Transform using options
429    /// let transform = TransformFunction::new(|host, options| {
430    ///     if let Some(opts) = options {
431    ///         if let Some(default_port) = opts.get("default_port").and_then(|v| v.as_u64()) {
432    ///             if host.port().is_none() {
433    ///                 return host.to_builder().port(default_port as u16).build();
434    ///             }
435    ///         }
436    ///     }
437    ///     host.clone()
438    /// });
439    /// ```
440    pub fn new<F>(func: F) -> Self
441    where
442        F: Fn(&Host, Option<&TransformFunctionOptions>) -> Host + Send + Sync + 'static,
443    {
444        struct HostOnlyTransform<F> {
445            func: F,
446        }
447
448        impl<F> Transform for HostOnlyTransform<F>
449        where
450            F: Fn(&Host, Option<&TransformFunctionOptions>) -> Host + Send + Sync,
451        {
452            fn transform_host(
453                &self,
454                host: &Host,
455                options: Option<&TransformFunctionOptions>,
456            ) -> Host {
457                (self.func)(host, options)
458            }
459        }
460
461        TransformFunction(Arc::new(HostOnlyTransform { func }))
462    }
463
464    /// Creates a new transform function from a type implementing the `Transform` trait.
465    ///
466    /// This constructor allows for full control over transformation of hosts, groups, and
467    /// defaults. Use this when you need to implement custom transformation logic for all
468    /// inventory entity types, or when you need to maintain state across transformations.
469    ///
470    /// # Type Parameters
471    ///
472    /// * `T` - A type implementing the `Transform` trait. The type must be `'static` to
473    ///   allow it to be stored in the `Arc` wrapper.
474    ///
475    /// # Parameters
476    ///
477    /// * `transform` - An instance of a type implementing `Transform`. The instance will
478    ///   be wrapped in an `Arc` for thread-safe sharing.
479    ///
480    /// # Returns
481    ///
482    /// Returns a new `TransformFunction` that applies the provided `Transform` implementation
483    /// to hosts, groups, and defaults.
484    ///
485    /// # Examples
486    ///
487    /// ```
488    /// # use genja_core::inventory::{Transform, TransformFunction, TransformFunctionOptions};
489    /// # use genja_core::inventory::{Host, Group, Defaults, BaseBuilderHost};
490    /// struct EnvironmentTransform {
491    ///     environment: String,
492    /// }
493    ///
494    /// impl Transform for EnvironmentTransform {
495    ///     fn transform_host(&self, host: &Host, _options: Option<&TransformFunctionOptions>) -> Host {
496    ///         // Prefix hostname with environment
497    ///         if let Some(hostname) = host.hostname() {
498    ///             host.to_builder()
499    ///                 .hostname(format!("{}-{}", self.environment, hostname))
500    ///                 .build()
501    ///         } else {
502    ///             host.clone()
503    ///         }
504    ///     }
505    ///
506    ///     fn transform_group(&self, group: &Group, _options: Option<&TransformFunctionOptions>) -> Group {
507    ///         // Apply environment-specific group modifications
508    ///         group.clone()
509    ///     }
510    ///
511    ///     fn transform_defaults(&self, defaults: &Defaults, _options: Option<&TransformFunctionOptions>) -> Defaults {
512    ///         // Apply environment-specific defaults
513    ///         defaults.clone()
514    ///     }
515    /// }
516    ///
517    /// let transform = TransformFunction::new_full(EnvironmentTransform {
518    ///     environment: "prod".to_string(),
519    /// });
520    /// ```
521    pub fn new_full<T>(transform: T) -> Self
522    where
523        T: Transform + 'static,
524    {
525        TransformFunction(Arc::new(transform))
526    }
527    /// Applies the transform function to a host.
528    ///
529    /// This method delegates to the underlying `Transform` implementation to modify
530    /// the provided host according to the transform logic. It's primarily used internally
531    /// by the inventory system when accessing hosts through views or during resolution.
532    ///
533    /// # Parameters
534    ///
535    /// * `host` - A reference to the host to transform
536    /// * `options` - Optional configuration data to pass to the transform function
537    ///
538    /// # Returns
539    ///
540    /// Returns a new `Host` instance with transformations applied. If no transform
541    /// logic is defined for hosts, returns a clone of the input host.
542    ///
543    /// # Examples
544    ///
545    /// ```
546    /// # use genja_core::inventory::{TransformFunction, Host, BaseBuilderHost};
547    /// let transform = TransformFunction::new(|host, _| {
548    ///     host.to_builder().port(2222).build()
549    /// });
550    ///
551    /// let host = Host::builder().hostname("10.0.0.1").build();
552    /// let transformed = transform.transform_host(&host, None);
553    /// assert_eq!(transformed.port(), Some(2222));
554    /// ```
555    pub fn transform_host(&self, host: &Host, options: Option<&TransformFunctionOptions>) -> Host {
556        self.0.transform_host(host, options)
557    }
558
559    /// Applies the transform function to a group.
560    ///
561    /// This method delegates to the underlying `Transform` implementation to modify
562    /// the provided group according to the transform logic. It's primarily used internally
563    /// by the inventory system when accessing groups through views.
564    ///
565    /// # Parameters
566    ///
567    /// * `group` - A reference to the group to transform
568    /// * `options` - Optional configuration data to pass to the transform function
569    ///
570    /// # Returns
571    ///
572    /// Returns a new `Group` instance with transformations applied. If no transform
573    /// logic is defined for groups, returns a clone of the input group.
574    ///
575    /// # Examples
576    ///
577    /// ```
578    /// # use genja_core::inventory::{Transform, TransformFunction, TransformFunctionOptions, Group, BaseBuilderHost};
579    /// struct GroupTransform;
580    /// impl Transform for GroupTransform {
581    ///     fn transform_group(&self, group: &Group, _options: Option<&TransformFunctionOptions>) -> Group {
582    ///         group.to_builder().port(443).build()
583    ///     }
584    /// }
585    ///
586    /// let transform = TransformFunction::new_full(GroupTransform);
587    /// let group = Group::builder().platform("linux").build();
588    /// let transformed = transform.transform_group(&group, None);
589    /// assert_eq!(transformed.port(), Some(443));
590    /// ```
591    pub fn transform_group(
592        &self,
593        group: &Group,
594        options: Option<&TransformFunctionOptions>,
595    ) -> Group {
596        self.0.transform_group(group, options)
597    }
598
599    /// Applies the transform function to inventory defaults.
600    ///
601    /// This method delegates to the underlying `Transform` implementation to modify
602    /// the provided defaults according to the transform logic. It's primarily used internally
603    /// by the inventory system when accessing defaults through `Inventory::defaults()`.
604    ///
605    /// # Parameters
606    ///
607    /// * `defaults` - A reference to the defaults to transform
608    /// * `options` - Optional configuration data to pass to the transform function
609    ///
610    /// # Returns
611    ///
612    /// Returns a new `Defaults` instance with transformations applied. If no transform
613    /// logic is defined for defaults, returns a clone of the input defaults.
614    ///
615    /// # Examples
616    ///
617    /// ```
618    /// # use genja_core::inventory::{Transform, TransformFunction, TransformFunctionOptions, Defaults, BaseBuilderHost};
619    /// struct DefaultsTransform;
620    /// impl Transform for DefaultsTransform {
621    ///     fn transform_defaults(&self, defaults: &Defaults, _options: Option<&TransformFunctionOptions>) -> Defaults {
622    ///         defaults.to_builder().username("admin").build()
623    ///     }
624    /// }
625    ///
626    /// let transform = TransformFunction::new_full(DefaultsTransform);
627    /// let defaults = Defaults::builder().port(22).build();
628    /// let transformed = transform.transform_defaults(&defaults, None);
629    /// assert_eq!(transformed.username(), Some("admin"));
630    /// ```
631    pub fn transform_defaults(
632        &self,
633        defaults: &Defaults,
634        options: Option<&TransformFunctionOptions>,
635    ) -> Defaults {
636        self.0.transform_defaults(defaults, options)
637    }
638}
639
640impl fmt::Debug for TransformFunction {
641    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
642        write!(f, "TransformFunction({:p})", Arc::as_ptr(&self.0))
643    }
644}
645
646/// The TransformFunctionOptions struct is a wrapper for serde_json::Value, any json data is accepted.
647/// Configuration options passed to transform functions when processing inventory entities.
648///
649/// `TransformFunctionOptions` is a wrapper around a JSON value that provides flexible,
650/// schema-free configuration data for transform functions. It allows passing arbitrary
651/// structured data to transforms without requiring predefined types or schemas.
652///
653/// The wrapper implements `Deref` and `DerefMut` to provide direct access to the underlying
654/// `serde_json::Value`, enabling use of all JSON value methods for accessing and manipulating
655/// the configuration data.
656///
657/// # Usage in Transforms
658///
659/// Transform functions receive an `Option<&TransformFunctionOptions>` parameter that can be
660/// used to access configuration data. The options are typically set on the `Inventory` using
661/// `InventoryBuilder::transform_function_options()`.
662///
663/// # JSON Structure
664///
665/// The underlying JSON value can be any valid JSON structure:
666/// - Object: `{"key": "value", "nested": {"data": 123}}`
667/// - Array: `["item1", "item2"]`
668/// - Primitive: `"string"`, `42`, `true`, `null`
669///
670/// # Examples
671///
672/// ## Creating Options
673///
674/// ```
675/// # use genja_core::inventory::TransformFunctionOptions;
676/// // Simple key-value options
677/// let options = TransformFunctionOptions::new(serde_json::json!({
678///     "default_port": 2222,
679///     "environment": "production"
680/// }));
681///
682/// // Nested configuration
683/// let options = TransformFunctionOptions::new(serde_json::json!({
684///     "ssh": {
685///         "port": 22,
686///         "timeout": 30
687///     },
688///     "netconf": {
689///         "port": 830,
690///         "timeout": 60
691///     }
692/// }));
693/// ```
694///
695/// ## Accessing Options in Transforms
696///
697/// ```
698/// # use genja_core::inventory::{Transform, TransformFunctionOptions, Host, Group, Defaults, BaseBuilderHost};
699/// struct PortTransform;
700///
701/// impl Transform for PortTransform {
702///     fn transform_host(&self, host: &Host, options: Option<&TransformFunctionOptions>) -> Host {
703///         // Access options using JSON value methods
704///         if let Some(opts) = options {
705///             if let Some(port) = opts.get("default_port").and_then(|v| v.as_u64()) {
706///                 if host.port().is_none() {
707///                     return host.to_builder().port(port as u16).build();
708///                 }
709///             }
710///         }
711///         host.clone()
712///     }
713/// }
714/// ```
715///
716/// ## Using with Inventory
717///
718/// ```
719/// # use genja_core::inventory::{Inventory, TransformFunction, TransformFunctionOptions, Host, Hosts, BaseBuilderHost};
720/// let transform = TransformFunction::new(|host, options| {
721///     if let Some(opts) = options {
722///         if let Some(prefix) = opts.get("hostname_prefix").and_then(|v| v.as_str()) {
723///             if let Some(hostname) = host.hostname() {
724///                 return host.to_builder()
725///                     .hostname(format!("{}{}", prefix, hostname))
726///                     .build();
727///             }
728///         }
729///     }
730///     host.clone()
731/// });
732///
733/// let options = TransformFunctionOptions::new(serde_json::json!({
734///     "hostname_prefix": "prod-"
735/// }));
736///
737/// let mut hosts = Hosts::new();
738/// hosts.add_host("router1", Host::builder().hostname("router1").build());
739///
740/// let inventory = Inventory::builder()
741///     .hosts(hosts)
742///     .transform_function(transform)
743///     .transform_function_options(options)
744///     .build();
745/// ```
746#[derive(
747    Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, DerefMacro, DerefMutMacro,
748)]
749pub struct TransformFunctionOptions(serde_json::Value);
750
751impl DerefTarget for TransformFunctionOptions {
752    type Target = serde_json::Value;
753}
754
755impl TransformFunctionOptions {
756    /// Creates a new `TransformFunctionOptions` instance from a JSON value.
757    ///
758    /// This constructor wraps any valid JSON value in a `TransformFunctionOptions` struct,
759    /// providing a flexible way to pass configuration data to transform functions. The JSON
760    /// value can be of any type: object, array, string, number, boolean, or null.
761    ///
762    /// The options are typically accessed within transform function implementations using
763    /// the `Deref` trait to access the underlying `serde_json::Value` methods like `get()`,
764    /// `as_str()`, `as_object()`, etc.
765    ///
766    /// # Parameters
767    ///
768    /// * `options` - A `serde_json::Value` containing the configuration data to be passed
769    ///   to transform functions. This can be any valid JSON structure created using the
770    ///   `serde_json::json!` macro or parsed from JSON text.
771    ///
772    /// # Returns
773    ///
774    /// Returns a new `TransformFunctionOptions` instance wrapping the provided JSON value.
775    ///
776    /// # Examples
777    ///
778    /// ```
779    /// # use genja_core::inventory::TransformFunctionOptions;
780    /// // Create options with an object
781    /// let options = TransformFunctionOptions::new(serde_json::json!({
782    ///     "default_port": 2222,
783    ///     "environment": "production"
784    /// }));
785    ///
786    /// // Create options with an array
787    /// let options = TransformFunctionOptions::new(serde_json::json!(["item1", "item2"]));
788    ///
789    /// // Create options with a primitive value
790    /// let options = TransformFunctionOptions::new(serde_json::json!("simple_string"));
791    /// ```
792    pub fn new(options: serde_json::Value) -> Self {
793        TransformFunctionOptions(options)
794    }
795}