FieldValue.h header

#include <ew/core/FieldValue.h>

Namespace ew::core

FieldValue class

class ew::core::FieldValue

One field's value, of whichever kind the field declares.

The single change that makes composite types possible. A string cannot hold a list, a map or a nested block, which is why none of those could exist while every value was one; and it is why building them on top of strings would have to be undone.

Copyable and comparable. A List or Map holds FieldValues of its own, so the storage is indirect – a value cannot contain itself by value – which is invisible to every caller.

Members

ew::core::entity::FieldValue::FieldValue()

An empty value: the field has nothing stored.

ew::core::entity::FieldValue::FieldValue(const FieldValue &other)

Copy constructor (deep: a List's elements are copied).

ew::core::entity::FieldValue::FieldValue(FieldValue &&other) noexcept

Move constructor.

FieldValue & ew::core::entity::FieldValue::operator=(const FieldValue &other)

Copy assignment.

FieldValue & ew::core::entity::FieldValue::operator=(FieldValue &&other) noexcept

Move assignment.

ew::core::entity::FieldValue::~FieldValue()

Destructor.

FieldValueKind ew::core::entity::FieldValue::kind() const

Which kind this holds.

bool ew::core::entity::FieldValue::isEmpty() const

Whether this holds nothing at all. See FieldValueKind::Empty.

QString ew::core::entity::FieldValue::asText() const

The text, choice, url or date exactly as stored; empty for every other kind.

Deliberately NOT a general "give me a string": use toDisplayString for that. This answers only for the kinds whose contents genuinely are text, so a caller that reaches for it on a Number gets nothing rather than a number silently spelled out.

double ew::core::entity::FieldValue::asNumber() const

The number, or 0 when this holds something else.

bool ew::core::entity::FieldValue::asBoolean() const

The boolean, or false when this holds something else.

ew::core::foundation::ContentId ew::core::entity::FieldValue::asReference() const

The referenced id, or a null id when this holds something else.

const std::vector< FieldValue > & ew::core::entity::FieldValue::asList() const

The list's elements, or an empty vector when this holds something else.

const std::vector< FieldValueEntry > & ew::core::entity::FieldValue::asMap() const

The map's entries, or an empty vector when this holds something else.

const std::vector< FieldValueEntry > & ew::core::entity::FieldValue::asBlock() const

The block's members, or an empty vector when this holds something else.

FieldValue ew::core::entity::FieldValue::member(const QString &key) const

The member stored under key, or an empty value when this is not a block or has no such member.

FieldValue ew::core::entity::FieldValue::withMember(const QString &key, FieldValue member) const

This value with key's member set to member, added at the end when it is new.

Returns a BLOCK even when called on an empty value, so filling in the first stat on an entity that has none does not need the caller to construct an empty block first.

QString ew::core::entity::FieldValue::toDisplayString() const

This value written for a person to read, whatever kind it is.

The one place a typed value becomes a string, so a list rendered in a table and the same list rendered in a tooltip cannot come out differently. A Number drops a trailing ".0" so a population of 812 does not read as "812.0".

bool ew::core::entity::FieldValue::operator==(const FieldValue &other) const

Whether two values hold the same kind and the same contents.

static FieldValue ew::core::entity::FieldValue::text(QString value)

A text value.

static FieldValue ew::core::entity::FieldValue::number(double value)

A numeric value.

static FieldValue ew::core::entity::FieldValue::boolean(bool value)

A boolean value.

static FieldValue ew::core::entity::FieldValue::date(QString isoDate)

A date, given in canonical ISO-8601 text.

static FieldValue ew::core::entity::FieldValue::reference(ew::core::foundation::ContentId id)

A reference to another content object.

static FieldValue ew::core::entity::FieldValue::choice(QString value)

A choice from a field's option set.

static FieldValue ew::core::entity::FieldValue::url(QString value)

A hyperlink.

static FieldValue ew::core::entity::FieldValue::list(std::vector< FieldValue > values)

An ordered sequence of values.

static FieldValue ew::core::entity::FieldValue::map(std::vector< FieldValueEntry > entries)

An ordered set of key/value pairs.

static FieldValue ew::core::entity::FieldValue::block(std::vector< FieldValueEntry > members)

One value per property of a property block, keyed by the block's field keys.

FieldValueCoercionError struct

struct ew::core::FieldValueCoercionError

What went wrong turning a string into a typed value.

Members

QString ew::core::entity::FieldValueCoercionError::text

The text that would not convert.

taxonomy::FieldType ew::core::entity::FieldValueCoercionError::type =

The type it was being converted to.

QString ew::core::entity::FieldValueCoercionError::message

One sentence naming what is wrong, for a migration report or a rejected edit.

bool operator==(const FieldValueCoercionError &, const FieldValueCoercionError &)=default

Errors compare equal when every field matches.

FieldValueEntry struct

struct ew::core::FieldValueEntry

One entry of a Map field.

Members

FieldValue ew::core::entity::FieldValueEntry::key

The key this entry is stored under.

FieldValue ew::core::entity::FieldValueEntry::value

The value stored under it.

bool operator==(const FieldValueEntry &, const FieldValueEntry &)=default

Entries compare equal when both key and value match.

Enumerations

enum class FieldValueKind { Empty, Text, Number, Boolean, Date, Reference, Choice, Url, List, Map, Block }

What a field value actually holds.

Deliberately a CLOSED set that mirrors taxonomy::FieldType, plus Empty. A value that has to be re-parsed at each use is a value that can be parsed differently at each use – the branching module recorded that reasoning for its own variables and it was never applied to entity fields, which is why every consumer of a Number field had its own idea of what "12,5" meant.

Functions

std::optional< FieldValue > ew::core::entity::coerceToFieldType(taxonomy::FieldType type, const QString &text, FieldValueCoercionError *error=nullptr)

text as a value of type, or the reason it will not convert.

The boundary the whole design turns on. Parsing happens here, once, on the way in – so that no consumer downstream has to parse and none of them can disagree. A value that will not convert is REPORTED rather than dropped or silently stored as text: a migration that quietly discarded "about forty" from a Number field would lose the only record that anybody had written it.

Empty text yields an empty value for every type, since "nothing here" is a legitimate state of any field and is not a conversion failure.

std::optional< FieldValueKind > ew::core::entity::fieldValueKindFromToken(QStringView token)

Parses a value kind from its serialization token; std::nullopt if unrecognized or absent.

FieldValueKind ew::core::entity::kindFor(taxonomy::FieldType type)

The kind a field of type stores.

std::vector< ew::core::foundation::ContentId > ew::core::entity::referencedIds(const FieldValue &value)

Every content id value links to, in the order it holds them.

Ask the value, not the field. A scalar Reference yields one id, a list of references yields all of them, and a map yields whatever its keys and values link to – so a consumer that walks links (backlinks, rename, export, the dangling check) is written once and keeps working when a property is changed from a single link into a list of them. Every consumer that instead tested field.type() == Reference would have silently stopped finding links the day lists shipped, and a link nobody can find is a link nothing protects.

Yields nothing for every other kind. Null ids are not included: a reference that points at nothing is an empty slot, not a link to a thing called nothing.

QString ew::core::entity::toToken(FieldValueKind kind)

Returns the stable serialization token for kind.

FieldValue ew::core::entity::withReferenceRetargeted(const FieldValue &value, ew::core::foundation::ContentId from, ew::core::foundation::ContentId to)

value with every link to from replaced by a link to to, at any depth.

The write-side twin of referencedIds, and needed for exactly the same reason: retargeting a link is not a special case of editing a field when the link is the fourth element of a list. Returns the value unchanged when it holds no link to from.

**A null to REMOVES the link** rather than storing a link to nothing – a scalar becomes empty, a list element is dropped, and a map entry whose KEY linked to from is dropped whole (an entry cannot be found again without its key) while one whose VALUE linked to it keeps the key and empties the value. This is what a merge's self-loop and a delete cascade both need: a list of five members whose third has been merged away must become a list of four, not a list of five with a hole in it.