Files
Structive/docs/EXTENSIONS.md
T
2026-08-07 16:21:44 +08:00

7.5 KiB

Structive Extension Architecture

中文

1. Extension role

Structive extensions add domain-specific interpretation without changing the Core structural model.

The intended dependency is:

application / adapter
        ↓
Structive extension
        ↓
Structive Property Core

Core does not include or link extension code.

2. Extension principle

An extension should normally add two things:

  1. one or more Attribute categories that express domain metadata;
  2. interpretation code that consumes those Attributes.

It should not create a parallel property registry when the existing Object_Schema already contains the required structure.

3. Current presentation extension

The current extension module defines four Attribute categories:

presentation::Label_Category
presentation::Description_Category
presentation::Group_Category
presentation::Order_Category

Convenience Attribute values:

presentation::label<"Temperature">
presentation::description<"Current device temperature">
presentation::group<"Environment">
presentation::order<10>

They can be attached directly to a normal Core property:

field<&Device::temperature>(
    key<"temperature">,
    unit<"C">,
    presentation::label<"Temperature">,
    presentation::description<"Current device temperature">,
    presentation::group<"Environment">,
    presentation::order<10>
)

No wrapper such as hint(...) is required.

4. Presentation interpretation

Include:

#include <structive/property/extensions/presentation.hpp>

Link:

target_link_libraries(my_target PRIVATE structive::property_extensions)

Describe a property by member pointer:

auto info = presentation::describe<&Device::temperature>(device.schema());

The result contains:

struct Presentation_Info {
    std::string_view key;
    std::string_view label;
    std::string_view description;
    std::string_view group;
    std::size_t order;
    bool has_order;
};

If no presentation label is declared, the extension uses the property key as the display label. This fallback is presentation policy and intentionally lives outside Core.

5. Inheritable extension Attributes

The presentation group Attribute is inheritable, so it may be placed in defaults(...):

return object<Device>(
    defaults(
        external_access<External_Access::read>,
        presentation::group<"Environment">
    ),
    field<&Device::temperature>(
        key<"temperature">,
        presentation::label<"Temperature">
    ),
    field<&Device::pressure>(
        key<"pressure">,
        presentation::label<"Pressure">
    )
);

The extension resolves an effective declared value through the same Core default mechanism.

label, description and order are not inheritable and therefore cannot be placed in defaults(...).

6. Designing a new extension Attribute

Example:

namespace my_adapter {
struct Json_Name_Category {};
template <Fixed_String Value>
struct Json_Name_Attribute {
    using attribute_category = Json_Name_Category;
    static constexpr bool single_valued = true;
    static constexpr bool inheritable = false;
    static constexpr auto value = Value;
};
template <Fixed_String Value>
inline constexpr Json_Name_Attribute<Value> json_name{};
}

Use it in a schema:

field<&Device::temperature>(
    key<"temperature">,
    my_adapter::json_name<"temp">
)

The Core stores it without needing to know what JSON means.

7. Reading extension Attributes

For a property descriptor:

using Property = std::remove_cvref_t<decltype(property)>;
if constexpr (Property::has_attribute<my_adapter::Json_Name_Category>) {
    const auto& value = property.attribute<my_adapter::Json_Name_Category>();
}

For inheritable categories, use effective declared lookup:

if constexpr (has_declared_effective_attribute_v<Schema, Index, My_Category>) {
    const auto& value = declared_effective_attribute<Index, My_Category>(schema);
}

This checks the property declaration first and then object defaults.

8. Multi-valued metadata

Core uniqueness is category-driven. An Attribute with single_valued = true and a non-void attribute_category may only appear once in one declaration.

When a domain needs repeated annotations, design that metadata so it does not claim single-valued category uniqueness, then consume it through for_each_attribute(...).

Do not force naturally repeated metadata into one large unrelated object merely to satisfy a single-valued design.

A future extension may reasonably own metadata and interpretation for areas such as:

  • serialization naming and omission rules;
  • RPC exposure rules;
  • UI labels, groups and editor hints;
  • database column mapping;
  • configuration-file mapping;
  • domain documentation generation.

These are examples of extension domains, not currently implemented features.

The important boundary is that Core should not gain a direct dependency on their libraries or domain types.

10. Core metadata may still be consumed

An extension is allowed to interpret Core-owned categories when those categories are part of the extension's input contract.

For example, an external adapter may use:

  • property key as the default protocol name;
  • External_Access to determine exposure;
  • sensitive to decide whether its own logs should omit a value.

The extension may choose a policy based on those attributes, but it should not change what Core itself means by them.

11. Fallback behavior belongs to the consumer

The presentation extension demonstrates the intended rule:

no label Attribute
    ↓
presentation extension chooses property key as label

Core does not invent that fallback because a different consumer may want a different behavior.

The same principle should apply to future adapters. Defaults that exist only to make one domain pleasant should remain in that domain.

12. Avoid extension-to-Core semantic leakage

Bad direction:

JSON adapter needs alias support
    ↓
Core adds JSON_Alias_Category and JSON naming logic

Preferred direction:

JSON extension defines Json_Name_Category
    ↓
JSON extension interprets it
    ↓
Core remains domain-neutral

13. Linked code versus header-only metadata

Attribute definitions can often be header-only. Interpretation may be header-only or linked depending on implementation needs.

The current presentation extension demonstrates a linked extension target. presentation::describe() performs compile-time Attribute selection and calls linked make_presentation_info() for the concrete result construction/fallback step.

Do not move linked implementation into Core merely because the extension is small.

14. Extension review checklist

Before accepting a new extension feature, ask:

  1. Does this concept belong to Core or to one consumer domain?
  2. Can it be represented through the existing Attribute protocol?
  3. Does the extension own the Attribute category it interprets?
  4. Is an inheritable Attribute truly an object-wide default policy?
  5. Is fallback behavior kept inside the extension?
  6. Is the existing schema reused rather than duplicated?
  7. Does the dependency still point from extension to Core?
  8. Has Core remained free of third-party domain types?
  9. Does the extension preserve the existing meaning of Core access, validation and synchronization?

If the answer to the dependency or semantic-boundary questions is no, the layering should be redesigned before code is added.