Module :: former
A flexible implementation of the Builder pattern supporting nested builders and collection-specific subformers.
The Builder pattern allows you to construct objects step by step, using only the steps you need. Any fields not explicitly set will receive default values. By implementing this pattern, you can avoid passing numerous parameters into your constructors.
This crate offers specialized subformers for common Rust collections, enabling the construction of complex data structures in a fluent and intuitive manner. Additionally, it provides the ability to define and reuse formers as subformers within other formers.
How Former Works
- Derivation: By deriving
Formeron a struct, you automatically generate builder methods for each field. - Fluent Interface: Each field's builder method allows for setting the value of that field and returns a mutable reference to the builder, enabling method chaining.
- Optional Fields: Optional fields can be easily handled without needing to explicitly set them to
None. - Finalization: The
.form()method finalizes the building process and returns the constructed struct instance. - Subforming: If a field has its own former defined or is a container of items for which a former is defined, it can be used as a subformer.
This approach abstracts away the need for manually implementing a builder for each struct, making the code more readable and maintainable.
Comparison
The Former crate and the abstract Builder pattern concept share a common goal: to construct complex objects step-by-step, ensuring they are always in a valid state and hiding internal structures. Both use a fluent interface for setting fields and support default values for fields that aren't explicitly set. They also have a finalization method to return the constructed object (.form() in Former, build() in traditional Builder).
However, the Former crate extends the traditional Builder pattern by automating the generation of builder methods using macros. This eliminates the need for manual implementation, which is often required in the abstract concept. Additionally, Former supports nested builders and subformers for complex data structures, allowing for more sophisticated object construction.
Advanced features such as custom setters, subformer reuse, storage-specific fields, mutators, and context management further differentiate Former from the traditional approach, which generally focuses on simpler use-cases without these capabilities. Moreover, while the traditional Builder pattern often includes a director class to manage the construction process, Former is not responsible for that aspect.
Example : Trivial
The provided code snippet illustrates a basic use-case of the Former, which is used to apply the builder pattern for to construct complex objects step-by-step, ensuring they are always in a valid state and hiding internal structures.
#
#
#
# #
#
#
#
# #
Try out cargo run --example former_trivial.
See code.
Example : Custom and Alternative Setters
With help of Former, it is possible to define multiple versions of a setter for a single field, providing the flexibility to include custom logic within the setter methods. This feature is particularly useful when you need to preprocess data or enforce specific constraints before assigning values to fields. Custom setters should have unique names to differentiate them from the default setters generated by Former, allowing for specialized behavior while maintaining clarity in your code.
#
#
#
# #
In the example above showcases a custom alternative setter, word_exclaimed, which appends an exclamation mark to the input string before storing it. This approach allows for additional processing or validation of the input data without compromising the simplicity of the builder pattern.
Try out cargo run --example former_custom_setter.
See code.
Example : Custom Setter Overriding
But it's also possible to completely override setter and write its own from scratch. For that use attribe [ setter( false ) ] to disable setter.
#
#
#
# #
In the example above, the default setter for word is disabled, and a custom setter is defined to automatically append an exclamation mark to the string. This method allows for complete control over the data assignment process, enabling the inclusion of any necessary logic or validation steps.
Try out cargo run --example former_custom_setter_overriden.
See code.
Example : Custom Defaults
The Former crate enhances struct initialization by allowing the specification of custom default values for fields through the default attribute. This feature not only provides a way to set initial values for struct fields without relying on the Default trait but also adds flexibility in handling cases where a field's type does not implement Default, or a non-standard default value is desired.
#
#
#
# #
The above code snippet showcases the Former crate's ability to initialize struct fields with custom default values:
- The
numberfield is initialized to5. - The
greetingfield defaults to a greeting message, "Hello, Former!". - The
numbersfield starts with a vector containing the integers10,20, and30.
This approach significantly simplifies struct construction, particularly for complex types or where defaults beyond the Default trait's capability are required. By utilizing the default attribute, developers can ensure their structs are initialized safely and predictably, enhancing code clarity and maintainability.
Try out cargo run --example former_custom_defaults.
See code.
Concept of Storage and Former
Storage is temporary storage structure holds the intermediate state of an object during its construction.
Purpose of Storage:
- Intermediate State Holding: Storage serves as a temporary repository for all the partially set properties and data of the object being formed. This functionality is essential in situations where the object's completion depends on multiple, potentially complex stages of configuration.
- Decoupling Configuration from Instantiation: Storage separates the accumulation of configuration states from the actual creation of the final object. This separation fosters cleaner, more maintainable code, allowing developers to apply configurations in any order and manage interim states more efficiently, without compromising the integrity of the final object.
Storage is not just a passive collection; it is an active part of a larger ecosystem that includes the former itself, a context, and a callback (often referred to as FormingEnd):
- Former as an Active Manager: The former is responsible for managing the storage, utilizing it to keep track of the object's evolving configuration. It orchestrates the formation process by handling intermediate states and preparing the object for its final form.
- Contextual Flexibility: The context associated with the former adds an additional layer of flexibility, allowing the former to adjust its behavior based on the broader circumstances of the object's formation. This is particularly useful when the forming process involves conditions or states external to the object itself.
- FormingEnd Callback: The
FormingEndcallback is a dynamic component that defines the final steps of the forming process. It can modify the storage based on final adjustments, validate the object's readiness, or integrate the object into a larger structure, such as embedding it as a subformer within another structure.
These elements work in concert to ensure that the forming process is not only about building an object step-by-step but also about integrating it seamlessly into larger, more complex structures or systems.
Concept of subformer
Subformers are specialized builders used within the former to construct nested or collection-based data structures like vectors, hash maps, and hash sets. They simplify the process of adding elements to these structures by providing a fluent interface that can be seamlessly integrated into the overall builder pattern of a parent struct. This approach allows for clean and intuitive initialization of complex data structures, enhancing code readability and maintainability.
Types of Setters / Subformers
Understanding the distinctions among the types of setters or subformers is essential for effectively employing the builder pattern in object construction. Each type of setter is designed to meet specific needs in building complex, structured data entities:
-
Scalar Setter: Handles the direct assignment of scalar values or simple fields within an entity. These setters manage basic data types or individual fields and do not involve nested formers or complex structuring.
-
Subform Collection Setter: Facilitates the management of a collection as a whole by returning a former that provides an interface to configure the entire collection. This setter is beneficial for applying uniform configurations or validations to all elements in a collection, such as a
HashMapof children. -
Subform Entry Setter: This setter allows for the individual formation of elements within a collection. It returns a former for each element, enabling detailed configuration and addition of complex elements within collections, exemplified by managing
Childentities within aParent'sHashMap. -
Subform Scalar Setter: Similar to the subform entry setter but designed for scalar fields that have a former implementation. This setter does not collect instances into a collection because there is no collection involved, only a scalar field. It is used when the scalar field itself needs to be configured or modified through its dedicated former.
These setters ensure that developers can precisely and efficiently set properties, manage collections, and configure complex structures within their applications.
Example : Collection Setter for a Vector
This example demonstrates how to employ the Former to configure a Vec using a collection setter in a structured manner.
#
#
#
# #
Try out cargo run --example former_collection_vector.
See code.
Example : Collection Setter for a Hashmap
This example demonstrates how to effectively employ the Former to configure a HashMap using a collection setter.
#
#
#
# #
Try out cargo run --example former_collection_hashmap.
See code.
Example : Collection Setter for a Hashset
This example demonstrates the use of the Former to build a collection_tools::HashSet through subforming.
#
#
#
#
Try out cargo run --example former_collection_hashset.
See code.
Example : Custom Scalar Setter
This example demonstrates the implementation of a scalar setter using the Former. Unlike the more complex subform and collection setters shown in previous examples, this example focuses on a straightforward approach to directly set a scalar value within a parent entity. The Parent struct manages a HashMap of Child entities, and the scalar setter is used to set the entire HashMap directly.
The child function within ParentFormer is a custom subform setter that plays a crucial role. It uniquely employs the ChildFormer to add and configure children by their names within the parent's builder pattern. This method demonstrates a powerful technique for integrating subformers that manage specific elements of a collection—each child entity in this case.
#
#
#
# #
In this example, the Parent struct functions as a collection for multiple Child structs, each identified by a unique child name. The ParentFormer implements a custom method child, which serves as a subformer for adding Child instances into the Parent.
- Child Definition: Each
Childconsists of anameand adescription, and we deriveFormerto enable easy setting of these properties using a builder pattern. - Parent Definition: It holds a collection of
Childobjects in aHashMap. The#[setter(false)]attribute is used to disable the default setter, and a custom methodchildis defined to facilitate the addition of children with specific attributes. - Custom Subformer Integration: The
childmethod in theParentFormerinitializes aChildFormerwith a closure that integrates theChildinto theParent'schildmap upon completion.
Try out cargo run --example former_custom_scalar_setter.
See code.
Example : Custom Subform Scalar Setter
Implementation of a custom subform scalar setter using the Former.
This example focuses on the usage of a subform scalar setter to manage complex scalar types within a parent structure. Unlike more general subform setters that handle collections, this setter specifically configures scalar fields that have their own formers, allowing for detailed configuration within a nested builder pattern.
#
# #
#
# // Ensures the example only compiles when the appropriate features are enabled.
#
# #
Example : Custom Subform Collection Setter
This example demonstrates the use of collection setters to manage complex nested data structures with the Former, focusing on a parent-child relationship structured around a collection HashMap. Unlike typical builder patterns that add individual elements using subform setters, this example uses a collection setter to manage the entire collection of children.
The child function within ParentFormer is a custom subform setter that plays a crucial role. It uniquely employs the ChildFormer to add and configure children by their names within the parent's builder pattern. This method demonstrates a powerful technique for integrating subformers that manage specific elements of a collection—each child entity in this case.
#
#
// Ensure the example only compiles when the appropriate features are enabled.
#
# #
Try out cargo run --example former_custom_subform_collection.
See code.
Example : Custom Subform Entry Setter
This example illustrates the implementation of nested builder patterns using the Former, emphasizing a parent-child relationship. Here, the Parent struct utilizes ChildFormer as a custom subformer to dynamically manage its child field—a HashMap. Each child in the HashMap is uniquely identified and configured via the ChildFormer.
The child function within ParentFormer is a custom subform setter that plays a crucial role. It uniquely employs the ChildFormer to add and configure children by their names within the parent's builder pattern. This method demonstrates a powerful technique for integrating subformers that manage specific elements of a collection—each child entity in this case.
#
#
# // Ensure the example only compiles when the appropriate features are enabled.
#
# #
Try out cargo run --example former_custom_subform_entry.
See code.
General Collection Interface
There are suite of traits designed to abstract and enhance the functionality of collection data structures within the forming process. These traits are integral to managing the complexity of collection operations, such as adding, modifying, and converting between different representations within collections like vectors, hash maps, etc. They are especially useful when used in conjunction with the collection attribute in the former macro, which automates the implementation of these traits to create robust and flexible builder patterns for complex data structures.
- [
Collection] - Defines basic functionalities for collections, managing entries and values, establishing the fundamental operations required for any custom collection implementation in forming processes. - [
EntryToVal] - Facilitates the conversion of collection entries to their value representations, crucial for operations that treat collection elements more abstractly as values. - [
ValToEntry] - Provides the reverse functionality ofEntryToVal, converting values back into entries, which is essential for operations that require adding or modifying entries in the collection based on value data. - [
CollectionAdd] - Adds functionality for inserting entries into a collection, considering collection-specific rules such as duplication handling and order preservation, enhancing the usability of collections in forming scenarios. - [
CollectionAssign] - Extends the collection functionality to replace all existing entries with new ones, enabling bulk updates or complete resets of collection contents, which is particularly useful in dynamic data environments.
Custom Collection Former
Collection interface is defined in the crate and implemented for collections like vectors, hash maps, etc, but if you want to use non-standard collection you can implement collection interface for the collection. This example demonstrate how to do that.
Try out cargo run --example former_custom_collection.
See code.
Concept of Mutator
Provides a mechanism for mutating the context and storage just before the forming process is completed.
The FormerMutator trait allows for the implementation of custom mutation logic on the internal state
of an entity (context and storage) just before the final forming operation is completed. This mutation
occurs immediately before the FormingEnd callback is invoked.
Use cases of Mutator
- Applying last-minute changes to the data being formed.
- Setting or modifying properties that depend on the final state of the storage or context.
- Storage-specific fields which are not present in formed structure.
Storage-Specific Fields
Storage-specific fields are intermediate fields that exist only in the storage structure during the forming process. These fields are not present in the final formed structure but are instrumental in complex forming operations, such as conditional mutations, temporary state tracking, or accumulations.
These fields are used to manage intermediate data or state that aids in the construction of the final object but does not necessarily have a direct representation in the object's schema. For instance, counters, flags, or temporary computation results that determine the final state of the object.
The FormerMutator trait facilitates the implementation of custom mutation logic. It acts on the internal
state (context and storage) just before the final forming operation is completed, right before the FormingEnd
callback is invoked. This trait is crucial for making last-minute adjustments or computations based on the
accumulated state in the storage.
Mutator vs FormingEnd
Unlike FormingEnd, which is responsible for integrating and finalizing the formation process of a field within
a parent former, form_mutation directly pertains to the entity itself. This method is designed to be independent
of whether the forming process is occurring within the context of a superformer or if the structure is a standalone
or nested field. This makes form_mutation suitable for entity-specific transformations that should not interfere
with the hierarchical forming logic managed by FormingEnd.
Example : Mutator and Storage Fields
This example illustrates how to use the FormerMutator trait for implementing custom mutations
and demonstrates the concept of storage-specific fields in the forming process.
In this example, the fields a and b are defined only within the storage and used
within the custom mutator to enrich or modify the field c of the formed entity. This approach
allows for a richer and more flexible formation logic that can adapt based on the intermediate state
held within the storage.
#
#
#
# #
Try out cargo run --example former_custom_mutator.
See code.
Concept of Definitions
Definitions are utilized to encapsulate and manage generic parameters efficiently and avoid passing each parameter individually.
Two key definition Traits:
FormerDefinitionTypes:- This trait outlines the essential components involved in the formation process, including the types of storage, the form being created, and the context used. It focuses on the types involved rather than the termination of the formation process.
FormerDefinition:- Building upon
FormerDefinitionTypes, this trait incorporates theFormingEndcallback, linking the formation types with a definitive ending. It specifies how the formation process should conclude, which may involve validations, transformations, or integrations into larger structures. - The inclusion of the
Endtype parameter specifies the end conditions of the formation process, effectively connecting the temporary state held in storage to its ultimate form.
- Building upon
Overview of Formation Traits System
The formation process utilizes several core traits, each serving a specific purpose in the lifecycle of entity creation. These traits ensure that entities are constructed methodically, adhering to a structured pattern that enhances maintainability and scalability. Below is a summary of these key traits:
EntityToDefinition: Links entities to their respective formation definitions which dictate their construction process.EntityToFormer: Connects entities with formers that are responsible for their step-by-step construction.EntityToStorage: Specifies the storage structures that temporarily hold the state of an entity during its formation.FormerDefinition,FormerDefinitionTypes: Define the essential properties and ending conditions of the formation process, ensuring entities are formed according to predetermined rules and logic.Storage: Establishes the fundamental interface for storage types used in the formation process, ensuring each can initialize to a default state.StoragePreform: Describes the transformation of storage from a mutable, intermediate state into the final, immutable state of the entity, crucial for accurately concluding the formation process.FormerMutator: Allows for custom mutation logic on the storage and context immediately before the formation process completes, ensuring last-minute adjustments are possible.FormingEnd: Specifies the closure action at the end of the formation process, which can transform or validate the final state of the entity.FormingEndClosure: Provides a flexible mechanism for dynamically handling the end of the formation process using closures, useful for complex scenarios.FormerBegin: Initiates a subforming process, managing how entities begin their formation in terms of storage and context setup.
These traits collectively facilitate a robust and flexible builder pattern that supports complex object creation and configuration scenarios.
Example : Custom Definition
Define a custom former definition and custom forming logic, and apply them to a collection.
The example showcases how to accumulate elements into a collection and then transform them into a single result using a custom FormingEnd implementation. This pattern is useful for scenarios where the formation process involves aggregation or transformation of input elements into a different type or form.
#
#
#
# #
Index of Examples
- Custom Defaults - Former allows the specification of custom default values for fields through the
former( default )attribute. - Custom Definition - Define a custom former definition and custom forming logic, and apply them to a collection.
To add to your project
Try out from the repository