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}