|
@@ -0,0 +1,363 @@
|
|
|
|
|
+using Gee;
|
|
|
|
|
+using Invercargill;
|
|
|
|
|
+using Invercargill.Mapping;
|
|
|
|
|
+using InvercargillJson;
|
|
|
|
|
+
|
|
|
|
|
+namespace Statum.Model {
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * A Statum directive.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Directives are the commands the server returns to the frontend (as a
|
|
|
|
|
+ * JSON array served with the `application/vnd.statum+json` content type).
|
|
|
|
|
+ * Each directive is a JSON object discriminated by its `type` member.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This base class is abstract; use one of the concrete subclasses and the
|
|
|
|
|
+ * {@link from_json} / {@link to_json} helpers (or the array helpers) to move
|
|
|
|
|
+ * between {@link DirectiveDto} instances and their JSON representation.
|
|
|
|
|
+ */
|
|
|
|
|
+ public abstract class DirectiveDto : Object {
|
|
|
|
|
+
|
|
|
|
|
+ /** The wire `type` value that discriminates this directive. */
|
|
|
|
|
+ public abstract string discriminator { get; }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Returns the directive's own field properties (excluding `type`),
|
|
|
|
|
+ * used to assemble the JSON object. Subclasses back this with their
|
|
|
|
|
+ * {@link PropertyMapper}.
|
|
|
|
|
+ */
|
|
|
|
|
+ protected abstract Properties build_field_properties() throws GLib.Error;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Serialises this directive to a {@link JsonObject} (including the
|
|
|
|
|
+ * `type` discriminator).
|
|
|
|
|
+ */
|
|
|
|
|
+ public JsonObject to_json_object() throws GLib.Error {
|
|
|
|
|
+ var obj = new JsonObject();
|
|
|
|
|
+ obj.set_native<string>("type", discriminator);
|
|
|
|
|
+ foreach (var field in build_field_properties()) {
|
|
|
|
|
+ obj.set(field.key, new JsonElement.from_element(field.value));
|
|
|
|
|
+ }
|
|
|
|
|
+ return obj;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Serialises this directive to a {@link JsonElement} (an object).
|
|
|
|
|
+ */
|
|
|
|
|
+ public JsonElement to_json() throws GLib.Error {
|
|
|
|
|
+ return to_json_object().as_element();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Deserialises a directive from a {@link JsonObject} by dispatching on
|
|
|
|
|
+ * its `type` member.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @throws ModelError.UNKNOWN_DIRECTIVE if `type` is not recognised.
|
|
|
|
|
+ */
|
|
|
|
|
+ public static DirectiveDto from_json(JsonObject json) throws GLib.Error {
|
|
|
|
|
+ var type = json.get_string("type");
|
|
|
|
|
+ if (type == null) {
|
|
|
|
|
+ throw new ModelError.UNKNOWN_DIRECTIVE("Directive is missing its \"type\" member");
|
|
|
|
|
+ }
|
|
|
|
|
+ switch ((!)type) {
|
|
|
|
|
+ case "navigate": return NavigateDirectiveDto.get_mapper().materialise(json);
|
|
|
|
|
+ case "post": return PostDirectiveDto.get_mapper().materialise(json);
|
|
|
|
|
+ case "set": return SetDirectiveDto.get_mapper().materialise(json);
|
|
|
|
|
+ case "clear": return ClearDirectiveDto.get_mapper().materialise(json);
|
|
|
|
|
+ case "error": return ErrorDirectiveDto.get_mapper().materialise(json);
|
|
|
|
|
+ case "subscribe": return SubscribeDirectiveDto.get_mapper().materialise(json);
|
|
|
|
|
+ case "unsubscribe": return UnsubscribeDirectiveDto.get_mapper().materialise(json);
|
|
|
|
|
+ case "notify": return NotifyDirectiveDto.get_mapper().materialise(json);
|
|
|
|
|
+ default:
|
|
|
|
|
+ throw new ModelError.UNKNOWN_DIRECTIVE(@"Unrecognised directive type \"$((!)type)\"");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Serialises a list of directives to the JSON array form used by every
|
|
|
|
|
+ * directive-returning endpoint.
|
|
|
|
|
+ */
|
|
|
|
|
+ public static JsonElement serialize_array(Gee.List<DirectiveDto> directives) throws GLib.Error {
|
|
|
|
|
+ var array = new JsonArray();
|
|
|
|
|
+ foreach (var directive in directives) {
|
|
|
|
|
+ array.add(directive.to_json_object().as_element());
|
|
|
|
|
+ }
|
|
|
|
|
+ return new JsonElement.from_elements(array);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Deserialises a JSON array of directives.
|
|
|
|
|
+ */
|
|
|
|
|
+ public static Gee.List<DirectiveDto> deserialize_array(JsonElement element) throws GLib.Error {
|
|
|
|
|
+ var result = new ArrayList<DirectiveDto>();
|
|
|
|
|
+ var array = element.as<JsonArray>();
|
|
|
|
|
+ foreach (var item in array) {
|
|
|
|
|
+ result.add(from_json(item.as<JsonObject>()));
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Redirects the frontend to a new URI.
|
|
|
|
|
+ */
|
|
|
|
|
+ public class NavigateDirectiveDto : DirectiveDto {
|
|
|
|
|
+
|
|
|
|
|
+ /** The URI to navigate to. */
|
|
|
|
|
+ public string uri { get; set; }
|
|
|
|
|
+
|
|
|
|
|
+ public override string discriminator { get { return "navigate"; } }
|
|
|
|
|
+
|
|
|
|
|
+ protected override Properties build_field_properties() throws GLib.Error {
|
|
|
|
|
+ return NavigateDirectiveDto.get_mapper().map_from(this);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public NavigateDirectiveDto() { }
|
|
|
|
|
+
|
|
|
|
|
+ public NavigateDirectiveDto.with_uri(string uri) {
|
|
|
|
|
+ this.uri = uri;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static PropertyMapper<NavigateDirectiveDto> get_mapper() {
|
|
|
|
|
+ return PropertyMapper.build_for<NavigateDirectiveDto>(cfg => {
|
|
|
|
|
+ cfg.map<string>("uri", o => o.uri, (o, v) => o.uri = v);
|
|
|
|
|
+ cfg.set_constructor(() => new NavigateDirectiveDto());
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Performs a full-page form POST navigation to a URI. A post directive is
|
|
|
|
|
+ * always executed after all other directives in the same response.
|
|
|
|
|
+ */
|
|
|
|
|
+ public class PostDirectiveDto : DirectiveDto {
|
|
|
|
|
+
|
|
|
|
|
+ /** The URI to perform a form post to. */
|
|
|
|
|
+ public string uri { get; set; }
|
|
|
|
|
+
|
|
|
|
|
+ /** Key/value pairs for the form data. */
|
|
|
|
|
+ public Properties data { get; set; }
|
|
|
|
|
+
|
|
|
|
|
+ public override string discriminator { get { return "post"; } }
|
|
|
|
|
+
|
|
|
|
|
+ protected override Properties build_field_properties() throws GLib.Error {
|
|
|
|
|
+ return PostDirectiveDto.get_mapper().map_from(this);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public PostDirectiveDto() { }
|
|
|
|
|
+
|
|
|
|
|
+ public PostDirectiveDto.with(string uri, Properties data) {
|
|
|
|
|
+ this.uri = uri;
|
|
|
|
|
+ this.data = data;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static PropertyMapper<PostDirectiveDto> get_mapper() {
|
|
|
|
|
+ return PropertyMapper.build_for<PostDirectiveDto>(cfg => {
|
|
|
|
|
+ cfg.map<string>("uri", o => o.uri, (o, v) => o.uri = v);
|
|
|
|
|
+ cfg.map<Properties>("data", o => o.data, (o, v) => o.data = v);
|
|
|
|
|
+ cfg.set_constructor(() => new PostDirectiveDto());
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets a slot to a new snapshot (wrapped in a frame). Set directives are
|
|
|
|
|
+ * idempotent on the client, keyed by the frame signature.
|
|
|
|
|
+ */
|
|
|
|
|
+ public class SetDirectiveDto : DirectiveDto {
|
|
|
|
|
+
|
|
|
|
|
+ /** A frame containing a snapshot. */
|
|
|
|
|
+ public FrameDto snapshot_frame { get; set; }
|
|
|
|
|
+
|
|
|
|
|
+ public override string discriminator { get { return "set"; } }
|
|
|
|
|
+
|
|
|
|
|
+ protected override Properties build_field_properties() throws GLib.Error {
|
|
|
|
|
+ return SetDirectiveDto.get_mapper().map_from(this);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public SetDirectiveDto() { }
|
|
|
|
|
+
|
|
|
|
|
+ public SetDirectiveDto.with_frame(FrameDto snapshot_frame) {
|
|
|
|
|
+ this.snapshot_frame = snapshot_frame;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static PropertyMapper<SetDirectiveDto> get_mapper() {
|
|
|
|
|
+ return PropertyMapper.build_for<SetDirectiveDto>(cfg => {
|
|
|
|
|
+ cfg.map_properties_with<FrameDto>("snapshot_frame",
|
|
|
|
|
+ o => o.snapshot_frame,
|
|
|
|
|
+ (o, v) => o.snapshot_frame = v,
|
|
|
|
|
+ FrameDto.get_mapper());
|
|
|
|
|
+ cfg.set_constructor(() => new SetDirectiveDto());
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Clears a slot.
|
|
|
|
|
+ */
|
|
|
|
|
+ public class ClearDirectiveDto : DirectiveDto {
|
|
|
|
|
+
|
|
|
|
|
+ /** The key of the slot to clear. */
|
|
|
|
|
+ public string key { get; set; }
|
|
|
|
|
+
|
|
|
|
|
+ public override string discriminator { get { return "clear"; } }
|
|
|
|
|
+
|
|
|
|
|
+ protected override Properties build_field_properties() throws GLib.Error {
|
|
|
|
|
+ return ClearDirectiveDto.get_mapper().map_from(this);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public ClearDirectiveDto() { }
|
|
|
|
|
+
|
|
|
|
|
+ public ClearDirectiveDto.with_key(string key) {
|
|
|
|
|
+ this.key = key;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static PropertyMapper<ClearDirectiveDto> get_mapper() {
|
|
|
|
|
+ return PropertyMapper.build_for<ClearDirectiveDto>(cfg => {
|
|
|
|
|
+ cfg.map<string>("key", o => o.key, (o, v) => o.key = v);
|
|
|
|
|
+ cfg.set_constructor(() => new ClearDirectiveDto());
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Reports an error to the frontend. Causes the originating `statum.act`
|
|
|
|
|
+ * promise to reject.
|
|
|
|
|
+ */
|
|
|
|
|
+ public class ErrorDirectiveDto : DirectiveDto {
|
|
|
|
|
+
|
|
|
|
|
+ /** A human-readable error message, if any. */
|
|
|
|
|
+ public string? message { get; set; }
|
|
|
|
|
+
|
|
|
|
|
+ /** An application-defined error code, if any. */
|
|
|
|
|
+ public string? code { get; set; }
|
|
|
|
|
+
|
|
|
|
|
+ /** Additional key/value error details, if any. */
|
|
|
|
|
+ public Properties? data { get; set; }
|
|
|
|
|
+
|
|
|
|
|
+ public override string discriminator { get { return "error"; } }
|
|
|
|
|
+
|
|
|
|
|
+ protected override Properties build_field_properties() throws GLib.Error {
|
|
|
|
|
+ return ErrorDirectiveDto.get_mapper().map_from(this);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public ErrorDirectiveDto() { }
|
|
|
|
|
+
|
|
|
|
|
+ public static PropertyMapper<ErrorDirectiveDto> get_mapper() {
|
|
|
|
|
+ return PropertyMapper.build_for<ErrorDirectiveDto>(cfg => {
|
|
|
|
|
+ cfg.map<string?>("message", o => o.message, (o, v) => o.message = v)
|
|
|
|
|
+ .undefined_when(o => o.message == null)
|
|
|
|
|
+ .when_undefined(o => o.message = null)
|
|
|
|
|
+ .when_null(o => o.message = null);
|
|
|
|
|
+ cfg.map<string?>("code", o => o.code, (o, v) => o.code = v)
|
|
|
|
|
+ .undefined_when(o => o.code == null)
|
|
|
|
|
+ .when_undefined(o => o.code = null)
|
|
|
|
|
+ .when_null(o => o.code = null);
|
|
|
|
|
+ cfg.map<Properties?>("data", o => o.data, (o, v) => o.data = v)
|
|
|
|
|
+ .undefined_when(o => o.data == null)
|
|
|
|
|
+ .when_undefined(o => o.data = null)
|
|
|
|
|
+ .when_null(o => o.data = null);
|
|
|
|
|
+ cfg.set_constructor(() => new ErrorDirectiveDto());
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Subscribes the client's realtime channel to a slot.
|
|
|
|
|
+ */
|
|
|
|
|
+ public class SubscribeDirectiveDto : DirectiveDto {
|
|
|
|
|
+
|
|
|
|
|
+ /** The key of the slot to subscribe to. */
|
|
|
|
|
+ public string key { get; set; }
|
|
|
|
|
+
|
|
|
|
|
+ public override string discriminator { get { return "subscribe"; } }
|
|
|
|
|
+
|
|
|
|
|
+ protected override Properties build_field_properties() throws GLib.Error {
|
|
|
|
|
+ return SubscribeDirectiveDto.get_mapper().map_from(this);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public SubscribeDirectiveDto() { }
|
|
|
|
|
+
|
|
|
|
|
+ public SubscribeDirectiveDto.with_key(string key) {
|
|
|
|
|
+ this.key = key;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static PropertyMapper<SubscribeDirectiveDto> get_mapper() {
|
|
|
|
|
+ return PropertyMapper.build_for<SubscribeDirectiveDto>(cfg => {
|
|
|
|
|
+ cfg.map<string>("key", o => o.key, (o, v) => o.key = v);
|
|
|
|
|
+ cfg.set_constructor(() => new SubscribeDirectiveDto());
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Unsubscribes the client's realtime channel from a slot.
|
|
|
|
|
+ */
|
|
|
|
|
+ public class UnsubscribeDirectiveDto : DirectiveDto {
|
|
|
|
|
+
|
|
|
|
|
+ /** The key of the slot to unsubscribe from. */
|
|
|
|
|
+ public string key { get; set; }
|
|
|
|
|
+
|
|
|
|
|
+ public override string discriminator { get { return "unsubscribe"; } }
|
|
|
|
|
+
|
|
|
|
|
+ protected override Properties build_field_properties() throws GLib.Error {
|
|
|
|
|
+ return UnsubscribeDirectiveDto.get_mapper().map_from(this);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public UnsubscribeDirectiveDto() { }
|
|
|
|
|
+
|
|
|
|
|
+ public UnsubscribeDirectiveDto.with_key(string key) {
|
|
|
|
|
+ this.key = key;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static PropertyMapper<UnsubscribeDirectiveDto> get_mapper() {
|
|
|
|
|
+ return PropertyMapper.build_for<UnsubscribeDirectiveDto>(cfg => {
|
|
|
|
|
+ cfg.map<string>("key", o => o.key, (o, v) => o.key = v);
|
|
|
|
|
+ cfg.set_constructor(() => new UnsubscribeDirectiveDto());
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Surfaces a transient notification to the user.
|
|
|
|
|
+ */
|
|
|
|
|
+ public class NotifyDirectiveDto : DirectiveDto {
|
|
|
|
|
+
|
|
|
|
|
+ /** An application-defined notification category string (e.g. "info"). */
|
|
|
|
|
+ public string kind { get; set; }
|
|
|
|
|
+
|
|
|
|
|
+ /** A human-readable message string. */
|
|
|
|
|
+ public string message { get; set; }
|
|
|
|
|
+
|
|
|
|
|
+ public override string discriminator { get { return "notify"; } }
|
|
|
|
|
+
|
|
|
|
|
+ protected override Properties build_field_properties() throws GLib.Error {
|
|
|
|
|
+ return NotifyDirectiveDto.get_mapper().map_from(this);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public NotifyDirectiveDto() { }
|
|
|
|
|
+
|
|
|
|
|
+ public NotifyDirectiveDto.with(string kind, string message) {
|
|
|
|
|
+ this.kind = kind;
|
|
|
|
|
+ this.message = message;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static PropertyMapper<NotifyDirectiveDto> get_mapper() {
|
|
|
|
|
+ return PropertyMapper.build_for<NotifyDirectiveDto>(cfg => {
|
|
|
|
|
+ cfg.map<string>("kind", o => o.kind, (o, v) => o.kind = v);
|
|
|
|
|
+ cfg.map<string>("message", o => o.message, (o, v) => o.message = v);
|
|
|
|
|
+ cfg.set_constructor(() => new NotifyDirectiveDto());
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|