The ProperytDescriptorBuilder allows fine-grained update operations to be staged prior to committing them to the PropertyType in the backing store.
Eventually, the Builder will offer three creation options (new, clone, derive). This story is only intended to deliver the 'new' option.
Design Sketch
- This design is informed with a thought towards DAHN design.
- The interactions between client and server are split following the general principle that the client is responsible for presentation and use case flow, while the server is responsible for security, persistence and validation.
- This implies the Use Case Controllers should be added to the stable of DAHN Visualizer types.
- In order to allow maximum latitude in use case flow, the Builder object should not make assumptions (or dictate) what information is known when. Thus, all fields within Builder objects should be
Option<> fields. This gives maximal freedom to use case designers by allowing them to fall anywhere on the spectrum between creating empty builders at the beginning of a flow to creating fully populated Builders the beginning of a flow.
- Note, the also means that the Builder API can be used by External Adaptors to create Descriptors.
- At some point, we may chose to persist
Builder objects as EntryTypes to allow continuous saving during the building process, but for now we are only maintaining builder information temporarily. Thus, Builders are not EntryTypes.
- Until we wrap them behind the MAP Uniform API, the Builder functions need to be visible via the Holochain Conductor, so they are exposed as
#[hdk_extern]functions.
- Builders incrementally accumulate state until they are committed. Since our servers are stateless, this means state needs to be either maintained on the client side and passed back and forth between the client and server.
- Builders have a lifecycle ( see Metaspace State Machine diagram).
- Builders are responsible for maintaining the Builder state and, on
commit, assigning Semantic Version Numbers to the HolonDescriptors they build as shown in the diagram.
Composite Properties properties may, themselves, reference a PropertyDescriptor which may or may not already be existing. PropertyDescriptorBuilders are used to create new PropertyDescriptors.
- PropertyDescriptors may either be dedicated or shared. Dedicated PropertyDescriptors are owned by their HolonDescriptor. Shared PropertyDescriptors are independently stored as Entries and referenced by HolonDescriptors. See Shared or Dedicated PropertyDescriptors for details.
Property Descriptor Builder Data Structure Definitions
#[hdk_entry_helper]
#[derive(new, Clone, PartialEq, Eq)]
pub struct PropertyDescriptorBuilder {
pub header: Option<TypeHeaderBuilder>,
pub descriptor_sharing: Option<PropertyDescriptorSharing>
pub details: Option<PropertyDescriptorDetailsBuilder>,
}
pub struct PropertyDescriptorUsageBuilder {
pub description: Option<String>,
pub descriptor: Option<PropertyDescriptor>,
}
pub struct PropertyDescriptorMap {
pub properties: Option<BTreeMap<String, PropertyDescriptorUsage>>,
}
#[hdk_entry_helper]
#[derive(new, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum PropertyDescriptorDetailsBuilder {
Boolean(BooleanDescriptorBuilder),
Composite(CompositeDescriptorBuilder),
//Enum(EnumDescriptorBuilder),
Integer(IntegerDescriptorBuilder),
String(StringDescriptorBuilder),
ValueCollection(ValueCollectionDescriptorBuilder), // can only contain collections of PropertyTypes (not Holons)
}
#[hdk_entry_helper]
#[derive(new, Default, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BooleanDescriptorBuilder {
pub is_fuzzy: Option<bool>, // if true, this property has FuzzyBoolean value, otherwise just true or false
}
#[hdk_entry_helper]
#[derive(new, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CompositeDescriptorBuilder {
pub properties: Option<PropertyDescriptorMapBuilder>,
}
#[hdk_entry_helper]
#[derive(new, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct IntegerDescriptorBuilder {
pub format: Option<IntegerFormat>,
pub min_value: Option<i64>,
pub max_value: Option<i64>,
}
#[hdk_entry_helper]
#[derive(Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub enum IntegerFormat {
I8(),
I16(),
I32(),
I64(),
// I128(),
U8(),
U16(),
U32(),
U64(),
// U128(),
}
#[hdk_entry_helper]
#[derive(new, Default, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct StringDescriptorBuilder {
pub min_length: Option<u32>,
pub max_length: Option<u32>,
//pattern: Option<String>,
}
#[hdk_entry_helper]
#[derive(new, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ValueCollectionDescriptorBuilder {
pub contains_items_of_type: Option<HolonReference>,
pub min_items: Option<u32>,
pub max_items: Option<u32>,
pub unique_items: Option<bool>, // true means duplicate items are not allowed
pub is_ordered: Option<bool>, // if items have an intrinsic order
}
Property Descriptor Builder Creational Operations:
This function creates a new PropertyDescriptorBuilder as an object to incrementally stage updates. It is up to the caller to decide how much information to supply to this function -- the function itself accepts completely empty to fully populated parameters to be provided. Invoking commit on the builder will request creation of a new HolonDescriptor (with no prior versions) from the Builder and persisting it in the backing store.
new_holon_descriptor_builder(
type_name: Option<String>,
description: Option<String>,
descriptor_sharing: Option<PropertyDescriptorSharing>
properties: Option<PropertyDescriptorMap>,
) -> ExternResult<HolonDescriptorBuilder>
add_string_property_descriptor_builder(
map: PropertyDescriptorMapBuilder // the property map that will contain the new descriptor
type_name: String,
description: Option<String>,
min_length: u32,
max_length: u32,
) -> ExternResult<PropertyDescriptorMap>
This function creates a new (empty) PropertyDescriptorBuilder as an object to incrementally stage updates. At this stage, the BaseType of the PropertyDescriptor must be specified before updating its details. Once the Builder is fully populated (i.e., the human agent has specified values for all the meta-properties of the PropertyDescriptor, the PropertyDescriptorBuilder can then be:
- committed to persist this propertyType as an independent (a.k.a., shared propertyType)
- AND/OR added to a PropertyMap within a HolonDescriptorBuilder.
getStringPropertyDescriptorBuilder(PropertyDescriptorBuilder)-> ExternResult
newProperty(
within_builder : HolonDescriptorBuilder,
at_path : PropertyPath,
) -> ExternResult<HolonDescriptorBuilder>
Creates a new (empty) PropertyDescriptorBuilder. At this point, the next step should be to select the BaseType for the property descriptor, so that the appropriate details can be captured.
addPropertyDescriptor(within_builder: HolonDescriptorBuilder, at_path: PropertyPath, ) -> ExternResult<HolonDescriptorBuilder>
Adds a new (empty) String property to the PropertyMap found via the specified at_path. The PropertyDescrib
pub fn new_composite_descriptor(
type_name: String,
description: String,
is_dependent: bool,
properties: PropertyDescriptorMap,
) -> Result<PropertyDescriptor, DescriptorsError> {
let details = PropertyDescriptorDetails::Composite(CompositeDescriptor::new(properties));
let desc = new_property_descriptor(
type_name,
description,
BaseType::Composite,
is_dependent,
details,
)?;
Ok(desc)
}
pub fn new_string_descriptor(
type_name: String,
description: String,
is_dependent: bool,
min_length: u32,
max_length: u32,
) -> Result<PropertyDescriptor, DescriptorsError> {
let details = PropertyDescriptorDetails::String(StringDescriptor::new(min_length, max_length));
let desc = new_property_descriptor(
type_name,
description,
BaseType::String,
is_dependent,
details,
)?;
Ok(desc)
}
pub fn new_integer_descriptor(
type_name: String,
description: String,
is_dependent: bool,
format: IntegerFormat,
min_value: i64,
max_value: i64,
) -> Result<PropertyDescriptor, DescriptorsError> {
let details =
PropertyDescriptorDetails::Integer(IntegerDescriptor::new(format, min_value, max_value));
let desc = new_property_descriptor(
type_name,
description,
BaseType::Integer,
is_dependent,
details,
)?;
Ok(desc)
}
pub fn new_boolean_descriptor(
type_name: String,
description: String,
is_dependent: bool,
is_fuzzy: bool,
) -> Result<PropertyDescriptor, DescriptorsError> {
let details = PropertyDescriptorDetails::Boolean(BooleanDescriptor::new(is_fuzzy));
let desc = new_property_descriptor(
type_name,
description,
BaseType::Boolean,
is_dependent,
details,
)?;
Ok(desc)
}
removeProperties(
property_map: PropertyDescriptorMap,
properties_to_remove: Vec<String>, // the list of property_names to remove from PropertyMap
)-> ExternResult<PropertyMap>
Marks an existing property as removed from a PropertyMap and returns the updated PropertyMap
FUTURE:
makeIdentifying -- adds an existing property to the list of identfying_properties -- returns Some/Error enum.
makeNotIdentifying -- removes a property from the list of identifying properties -- returns Some/Error enum.
The
ProperytDescriptorBuilderallows fine-grained update operations to be staged prior to committing them to the PropertyType in the backing store.Eventually, the Builder will offer three creation options (
new,clone,derive). This story is only intended to deliver the 'new' option.Design Sketch
Option<>fields. This gives maximal freedom to use case designers by allowing them to fall anywhere on the spectrum between creating empty builders at the beginning of a flow to creating fully populated Builders the beginning of a flow.Builderobjects asEntryTypesto allow continuous saving during the building process, but for now we are only maintaining builder information temporarily. Thus,Buildersare notEntryTypes.#[hdk_extern]functions.commit, assigning Semantic Version Numbers to theHolonDescriptorsthey build as shown in the diagram.Composite Propertiesproperties may, themselves, reference aPropertyDescriptorwhich may or may not already be existing.PropertyDescriptorBuildersare used to create newPropertyDescriptors.Property Descriptor Builder Data Structure Definitions
Property Descriptor Builder Creational Operations:
This function creates a new
PropertyDescriptorBuilderas an object to incrementally stage updates. It is up to the caller to decide how much information to supply to this function -- the function itself accepts completely empty to fully populated parameters to be provided. Invokingcommiton the builder will request creation of a new HolonDescriptor (with no prior versions) from the Builder and persisting it in the backing store.This function creates a new (empty)
PropertyDescriptorBuilderas an object to incrementally stage updates. At this stage, theBaseTypeof thePropertyDescriptormust be specified before updating its details. Once the Builder is fully populated (i.e., the human agent has specified values for all the meta-properties of the PropertyDescriptor, the PropertyDescriptorBuilder can then be:getStringPropertyDescriptorBuilder(PropertyDescriptorBuilder)-> ExternResult
Creates a new (empty) PropertyDescriptorBuilder. At this point, the next step should be to select the BaseType for the property descriptor, so that the appropriate details can be captured.
addPropertyDescriptor(within_builder: HolonDescriptorBuilder, at_path: PropertyPath, ) -> ExternResult<HolonDescriptorBuilder>Adds a new (empty) String property to the
PropertyMapfound via the specifiedat_path. The PropertyDescribpub fn new_composite_descriptor(
type_name: String,
description: String,
is_dependent: bool,
properties: PropertyDescriptorMap,
) -> Result<PropertyDescriptor, DescriptorsError> {
let details = PropertyDescriptorDetails::Composite(CompositeDescriptor::new(properties));
}
pub fn new_string_descriptor(
type_name: String,
description: String,
is_dependent: bool,
min_length: u32,
max_length: u32,
) -> Result<PropertyDescriptor, DescriptorsError> {
let details = PropertyDescriptorDetails::String(StringDescriptor::new(min_length, max_length));
let desc = new_property_descriptor(
type_name,
description,
BaseType::String,
is_dependent,
details,
)?;
Ok(desc)
}
pub fn new_integer_descriptor(
type_name: String,
description: String,
is_dependent: bool,
format: IntegerFormat,
min_value: i64,
max_value: i64,
) -> Result<PropertyDescriptor, DescriptorsError> {
let details =
PropertyDescriptorDetails::Integer(IntegerDescriptor::new(format, min_value, max_value));
let desc = new_property_descriptor(
type_name,
description,
BaseType::Integer,
is_dependent,
details,
)?;
Ok(desc)
}
pub fn new_boolean_descriptor(
type_name: String,
description: String,
is_dependent: bool,
is_fuzzy: bool,
) -> Result<PropertyDescriptor, DescriptorsError> {
let details = PropertyDescriptorDetails::Boolean(BooleanDescriptor::new(is_fuzzy));
let desc = new_property_descriptor(
type_name,
description,
BaseType::Boolean,
is_dependent,
details,
)?;
Ok(desc)
}
Marks an existing property as removed from a PropertyMap and returns the updated PropertyMap
FUTURE:
makeIdentifying -- adds an existing property to the list of identfying_properties -- returns Some/Error enum.
makeNotIdentifying -- removes a property from the list of identifying properties -- returns Some/Error enum.