using Invercargill.DataStructures; using InvercargillJson; using Inversion; namespace Statum { /** * Resolves Statum actions to/from their auto-assigned GUID endpoints; the * design follows the old Spry 0.1 `PathProvider`, removed in Spry 0.2. * * Every action registered via {@link StatumConfigurator.action} is assigned a * random GUID and served at `/_statum/action/{guid}` by a single * {@link ActionEndpoint}. The same GUID (looked up by type) is used when an * {@link Statum.Model.ActionDto} reference is authored for embedding in state. * * GUIDs are random per process start; this is fine because action references * are re-embedded into state on every entrypoint load, so a stale GUID never * survives a page render. */ public class ActionRegistry : Object { private Cryptography.EncryptionProvider encryption_provider = inject(); private Dictionary guid_to_type = new Dictionary(); private Dictionary type_to_guid = new Dictionary(); /** Registers an action type and assigns it a GUID (idempotent). */ public void register() { register_type(typeof(TAction)); } /** Registers an action type and assigns it a GUID (idempotent). */ public void register_type(Type type) { var name = type.name(); if (type_to_guid.has(name)) { return; } var guid = Uuid.string_random(); guid_to_type[guid] = type; type_to_guid[name] = guid; } /** The GUID assigned to `TAction` (registering it first if needed). */ public string guid_for() { return guid_for_type(typeof(TAction)); } /** The GUID assigned to `type` (registering it first if needed). */ public string guid_for_type(Type type) { var name = type.name(); string guid; if (type_to_guid.try_get(name, out guid)) { return guid; } register_type(type); type_to_guid.try_get(name, out guid); return guid; } /** Resolves a GUID back to its action type for dispatch, or `null`. */ public Type? resolve(string guid) { Type type; if (guid_to_type.try_get(guid, out type)) { return type; } return null; } /** The seal namespace for `TAction`'s private data (its type name). */ public string namespace_for() { return typeof(TAction).name(); } /** The seal namespace for `type`'s private data (its type name). */ public string namespace_for_type(Type type) { return type.name(); } /** * Authors an {@link Model.ActionDto} reference for `TAction` (no private * data) that the client can invoke via `stm-action`. */ public Model.ActionDto author(string method = "POST") { return new Model.ActionDto() { uri = @"/_statum/action/$(guid_for())", method = method }; } /** * Authors an {@link Model.ActionDto} reference for `TAction` carrying * `model` as sealed private data. The blob is sealed under * {@link namespace_for}'s namespace, which the matching * `TypedStatumAction` handler decrypts with its per-type * namespace. * * (Named distinctly from {@link author} because Vala does not permit * methods that overload on generic arity.) */ public Model.ActionDto author_private(TPrivate model, string method = "POST") throws GLib.Error { var blob = encryption_provider.author_properties( namespace_for(), GObjectMapping.to_properties((Object) model)); return new Model.ActionDto() { uri = @"/_statum/action/$(guid_for())", method = method, @private = blob }; } } }