DirectiveBuilder.vala 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. using Invercargill;
  2. using Invercargill.DataStructures;
  3. using Invercargill.Mapping;
  4. namespace Statum {
  5. /**
  6. * Fluent builder for the directive list an entrypoint/action returns.
  7. *
  8. * Started via {@link StatumEntrypoint.directives}/{@link StatumAction.directives}
  9. * (bound to the handler's {@link StateService}). Each mutator returns the
  10. * builder, so directives chain:
  11. *
  12. * ```
  13. * return directives()
  14. * .set(slot) // fresh slot: sign_slot -> set
  15. * .update(key, state) // mutate + auto-push to channels + sign -> set
  16. * .clear(other_key)
  17. * .notify("info", "Saved")
  18. * .navigate("/");
  19. * ```
  20. *
  21. * `set`/`update` are intents recorded in declaration order; pure directives
  22. * are stored inline. {@link build} is `async` and materialises the intents in
  23. * order (`update` yields to push to subscribed channels; `set` calls
  24. * `sign_slot`), preserving ordering, then returns the directives.
  25. */
  26. public class DirectiveBuilder : Object {
  27. private StateService state_service;
  28. private Series<Item> items = new Series<Item>();
  29. internal DirectiveBuilder(StateService state_service) {
  30. this.state_service = state_service;
  31. }
  32. /** Signs the (fresh) slot and emits a `set` directive for its frame. */
  33. public DirectiveBuilder set(Slot slot) {
  34. items.add(new Item() { kind = ItemKind.SLOT, slot = slot });
  35. return this;
  36. }
  37. /** Emits a `set` directive for an already-signed frame. */
  38. public DirectiveBuilder set_frame(Model.FrameDto frame) {
  39. items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.SetDirectiveDto.with_frame(frame) });
  40. return this;
  41. }
  42. /** Mutates the cached slot (`update` = sign + auto-push to channels) and emits a `set`. */
  43. public DirectiveBuilder update(string key, State state) {
  44. items.add(new Item() { kind = ItemKind.UPDATE, key = key, update_state = state });
  45. return this;
  46. }
  47. /** Emits a `clear` directive. */
  48. public DirectiveBuilder clear(string key) {
  49. items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.ClearDirectiveDto.with_key(key) });
  50. return this;
  51. }
  52. /** Emits an `invalidate` directive for a frame `signature` the server could not trust. */
  53. public DirectiveBuilder invalidate(string signature) {
  54. items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.InvalidateDirectiveDto.with_signature(signature) });
  55. return this;
  56. }
  57. /** Emits a `subscribe` directive (realtime channel). */
  58. public DirectiveBuilder subscribe(string key) {
  59. items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.SubscribeDirectiveDto.with_key(key) });
  60. return this;
  61. }
  62. /** Emits an `unsubscribe` directive. */
  63. public DirectiveBuilder unsubscribe(string key) {
  64. items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.UnsubscribeDirectiveDto.with_key(key) });
  65. return this;
  66. }
  67. /** Emits a `transmit` directive. */
  68. public DirectiveBuilder transmit(string key, bool retry = false) {
  69. items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.TransmitDirectiveDto.with(key, retry) });
  70. return this;
  71. }
  72. /** Emits a `notify` directive. */
  73. public DirectiveBuilder notify(string kind, string message) {
  74. items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.NotifyDirectiveDto.with(kind, message) });
  75. return this;
  76. }
  77. /** Emits an `error` directive. */
  78. public DirectiveBuilder error(string message, string? code = null, Properties? data = null) {
  79. items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.ErrorDirectiveDto() {
  80. message = message, code = code, data = data
  81. }});
  82. return this;
  83. }
  84. /** Emits a terminal `post` directive (full-page form POST). */
  85. public DirectiveBuilder post(string uri, Properties data) {
  86. items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.PostDirectiveDto.with(uri, data) });
  87. return this;
  88. }
  89. /** Emits a terminal `navigate` directive. */
  90. public DirectiveBuilder navigate(string uri) {
  91. items.add(new Item() { kind = ItemKind.DIRECTIVE, directive = new Model.NavigateDirectiveDto.with_uri(uri) });
  92. return this;
  93. }
  94. /** Materialises intents and returns the directive lot, in declaration order. */
  95. public async Lot<Model.DirectiveDto> build() throws GLib.Error {
  96. var directives = new Series<Model.DirectiveDto>();
  97. foreach (var item in items) {
  98. switch (item.kind) {
  99. case ItemKind.DIRECTIVE:
  100. if (item.directive != null) {
  101. directives.add(item.directive);
  102. }
  103. break;
  104. case ItemKind.SLOT:
  105. if (item.slot != null) {
  106. var frame = state_service.sign_slot(((!)item.slot).id);
  107. if (frame != null) {
  108. directives.add(new Model.SetDirectiveDto.with_frame((!)frame));
  109. }
  110. }
  111. break;
  112. case ItemKind.UPDATE:
  113. var frame = yield state_service.update(item.key, item.update_state);
  114. directives.add(new Model.SetDirectiveDto.with_frame(frame));
  115. break;
  116. }
  117. }
  118. return directives;
  119. }
  120. /** Alias for {@link build}. */
  121. public async Lot<Model.DirectiveDto> to_lot() throws GLib.Error {
  122. return yield build();
  123. }
  124. private enum ItemKind {
  125. DIRECTIVE,
  126. SLOT,
  127. UPDATE
  128. }
  129. private class Item : Object {
  130. public ItemKind kind;
  131. public Model.DirectiveDto? directive;
  132. public Slot? slot;
  133. public State? update_state;
  134. public string key;
  135. }
  136. }
  137. }