C API for `EntityReference`
What
Following on from #549, we must implement the C API for the EntityReference class and its related Manager member functions.
Why
This is a requirement for a functional C host.
Notes
Design sketch:
/**
* Entity reference destructor.
*
* Note: not paired with a constructor, since
* `Manager.createEntityReference` should always be used.
*/
void oa_EntityReference_dtor(oa_EntityReference_h handle) {
EntityReference* entityReference = handles::EntityReference::toInstance(handle);
delete entityReference;
}
/**
* Get a copy of the underlying entity reference string.
*
* Assumes noexcept.
*/
void oa_EntityReference_toString(oa_StringView* out, oa_EntityReference_h handle) {
const EntityReference* entityReference = handles::EntityReference::toInstance(handle);
openassetio::assignStringView(out, entityReference->toString());
}
/**
* Check whether a given string is an entity reference relevant
* to the manager.
*
* Assumes noexcept.
*/
bool oa_hostApi_Manager_isEntityReferenceString(oa_hostApi_Manager_h handle,
oa_ConstStringView entityReferenceString) {
const hostApi::ManagerPtr& manager = *handles::hostApi::SharedManager::toInstance(handle);
return manager->isEntityReferenceString(
{entityReferenceString.data, entityReferenceString.size});
}
/**
* Construct an EntityReference.
*
* This is the only way of creating an EntityReference in the C API. The caller is responsible
* for cleaning it up with oa_EntityReference_dtor.
*/
oa_ErrorCode oa_hostApi_Manager_createEntityReference(oa_StringView* err,
oa_EntityReference_h* out,
oa_hostApi_Manager_h handle,
oa_ConstStringView entityReferenceString) {
return errors::catchUnknownExceptionAsCode(err, [&] {
const hostApi::ManagerPtr& manager = *handles::hostApi::SharedManager::toInstance(handle);
std::optional<EntityReference> maybeEntityRef = manager->createEntityReferenceIfValid(
{entityReferenceString.data, entityReferenceString.size});
if (!maybeEntityRef) {
openassetio::assignStringView(err, "Invalid entity reference");
return oa_ErrorCode_kInvalidEntityReference;
}
*out = handles::EntityReference::toHandle(new EntityReference{std::move(*maybeEntityRef)});
return oa_ErrorCode_kOK;
});
}
Whilst sketching out the C API for BatchElementError in #557, it became apparent that we may need additional C functions for constructing a batch of entity reference objects.
For batch functions such as resolve we need to take a list of EntitiyReference objects. Naively, these could be created one-by-one using oa_hostApi_Manager_createEntityReference, and added to a C array of oa_EntityReference_h handles.
In C++ terms we then have a C array of pointers to EntityReference objects. The current design of our batch methods takes a std::vector<EntityReference> - i.e. a contiguous array. To convert from an array of pointers to a contiguous array means copying every element.
So we may wish to bind an oa_EntityReferenceList_h type, which wraps a std::vector, and add oa_hostApi_Manager_addEntityReference-like functions that append to the underlying std::vector. This would then be trivial to unpack in the C->C++ API.
This was actually assumed in a previous sketch: https://github.com/OpenAssetIO/OpenAssetIO/issues/490#issuecomment-1170008687, where the entity references argument to resolve is an unspecified oa_ConstEntityRefs_h. That sketch pre-dates the strongly typed EntityReference object, but the principle is the same.
A potential improvement to the proposed sketch would be to alter the argument oa_EntityReference_h* out to oa_EntityReference_h out, i.e. expect a pre-allocated EntityReference, which will be "filled in". This allows the host to re-use storage, rather than necessitate a dynamic allocation with every createEntityReference call.
Such a scheme would be particularly useful with respect to the above comment, i.e. for building a vector of entity references.
E.g.
oa_EntityReferenceList_h refListHandle = oa_EntityReferenceList_ctor();
oa_EntityReference_h refHandle = oa_EntityReference_ctor();
oa_hostApi_Manager_createEntityReference(err, refHandle, managerHandle, firstRefString);
// Assumed a `move` function that uses `std::move` internally, leaving
// the `refHandle` empty, but reusable.
oa_EntityReferenceList_move(refListHandle, refHandle)
oa_hostApi_Manager_createEntityReference(err, refHandle, managerHandle, secondRefString);
oa_EntityReferenceList_move(refListHandle, refHandle)
oa_EntityReference_dtor(refHandle);
oa_hostApi_Manager_resolve(..., refListHandle, ...);
oa_EntityReferenceList_dtor(refListHandle);
This seems quite efficient, but has the disadvantage that we allow entity reference objects to be constructed independently from createEntityReference. But then C devs are supposed to be grown-ups.
A possible variation that enforces validation as items are added to the list would be, as suggested in the comment above, to use a dedicated oa_hostApi_Manager_addEntityReference function
oa_ErrorCode oa_hostApi_Manager_addEntityReference(oa_StringView* err, oa_hostApi_Manager_h handle,
oa_EntityReferenceList_h refListHandle,
oa_ConstStringView entityReferenceString) {
return errors::catchUnknownExceptionAsCode(err, [&] {
const hostApi::ManagerPtr& manager = *handles::hostApi::SharedManager::toInstance(handle);
std::optional<EntityReference> maybeEntityRef = manager->createEntityReferenceIfValid(
{entityReferenceString.data, entityReferenceString.size});
if (!maybeEntityRef) {
openassetio::assignStringView(err, "Invalid entity reference");
return oa_ErrorCode_kInvalidEntityReference;
}
std::vector<EntityReference>& refList = *handles::EntityReferenceList::toInstance(refListHandle);
refList.push_back(std::move(*maybeEntityRef));
return oa_ErrorCode_kOK;
});
}
C-Api needs more holistic thinking, out of date.