Add CommandExecution to BlatherNotify

Christopher Vollick created

This follows a single command through a chain of steps and takes care of
the flow where sometimes I fetch a form before I submit it, and other
times I've already gotten the form on the last step, etc.

Change summary

lib/blather_notify.rb | 74 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 72 insertions(+), 2 deletions(-)

Detailed changes

lib/blather_notify.rb 🔗

@@ -4,6 +4,10 @@ require "blather/client/dsl"
 require "em_promise"
 require "timeout"
 
+require "ostruct"
+require "securerandom"
+require_relative "form_to_h"
+
 module BlatherNotify
 	extend Blather::DSL
 
@@ -38,6 +42,65 @@ module BlatherNotify
 		end
 	end
 
+	class CommandExecution
+		using FormToH
+
+		class FormErrorResponse < RuntimeError; end
+
+		def initialize(blather, server, node)
+			@blather = blather
+			@server = server
+			@node = node
+			@sessionid = nil
+		end
+
+		def fetch_and_submit(**form)
+			get_form.then { |response|
+				@sessionid ||= response.sessionid
+
+				validate_form(form, response.form)
+
+				@blather.write_with_promise(@blather.command(
+					@node, @sessionid, form: form.to_form(:submit), server: @server
+				))
+			}.then(&method(:check_for_error)).then { |response|
+				@last_response = response
+				OpenStruct.new(response.form.to_h)
+			}
+		end
+
+	protected
+
+		def check_for_error(response)
+			if response.note&.[]("type") == "error"
+				raise FormErrorResponse, response.note.text
+			end
+
+			response
+		end
+
+		def validate_form(to_submit, received)
+			to_submit.each_key do |key|
+				raise "No field #{key}" unless received.field(key.to_s)
+			end
+		end
+
+		def get_form
+			# If we already got a form on the last submit then
+			# assume we should fill that out here.
+			# If not, then move next to find the form
+			if @last_response&.form&.form?
+				EMPromise.resolve(@last_response)
+			else
+				@blather.write_with_promise(@blather.command(
+					@node,
+					@sessionid,
+					server: @server
+				))
+			end
+		end
+	end
+
 	@ready = Queue.new
 
 	when_ready { @ready << :ready }
@@ -104,9 +167,12 @@ module BlatherNotify
 		promise
 	end
 
-	def self.command(node, sessionid=nil, action: :execute, form: nil)
+	def self.command(
+		node, sessionid=nil,
+		server: CONFIG[:sgx_jmp], action: :execute, form: nil
+	)
 		Blather::Stanza::Iq::Command.new.tap do |cmd|
-			cmd.to = CONFIG[:sgx_jmp]
+			cmd.to = server
 			cmd.node = node
 			cmd.command[:sessionid] = sessionid if sessionid
 			cmd.action = action
@@ -135,4 +201,8 @@ module BlatherNotify
 
 		@default_pubsub.publish(xml)
 	end
+
+	def self.command_execution(server, node)
+		CommandExecution.new(self, server, node)
+	end
 end