Class: Inquirex::Definition

Inherits:
Object
  • Object
show all
Defined in:
lib/inquirex/definition.rb

Overview

Immutable, versionable flow graph. Maps step ids to Node objects and defines the entry point. Carries optional metadata (id, version, title, subtitle, brand) for the JS widget. Supports JSON round-trip serialization; lambdas are stripped on serialization.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(start_step_id:, nodes:, id: nil, version: "1.0.0", meta: {}, accumulators: {}, actions: [], allowed_domains: []) ⇒ Definition

Returns a new instance of Definition.

Parameters:

  • start_step_id (Symbol)

    id of the initial step

  • nodes (Hash<Symbol, Node>)

    all steps keyed by id

  • id (String, nil) (defaults to: nil)

    flow identifier

  • version (String) (defaults to: "1.0.0")

    semver

  • meta (Hash) (defaults to: {})

    frontend metadata

  • accumulators (Hash<Symbol, Accumulator>) (defaults to: {})

    named running totals

  • actions (Array<Actions::Action>) (defaults to: [])

    post-completion actions, in order

  • allowed_domains (Array<String>) (defaults to: [])

    hosts outbound effects (webhook) may send answers to; "example.com" exact, "*.example.com" subdomains

Raises:



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/inquirex/definition.rb', line 35

def initialize(start_step_id:, nodes:, id: nil, version: "1.0.0", meta: {},
  accumulators: {}, actions: [], allowed_domains: [])
  @id = id
  @version = version
  @meta = meta.freeze
  @start_step_id = start_step_id.to_sym
  @steps = nodes.freeze
  @accumulators = accumulators.freeze
  @actions = actions.freeze
  @allowed_domains = normalize_domains(allowed_domains)
  validate!
  freeze
end

Instance Attribute Details

#accumulatorsObject (readonly)

Returns the value of attribute accumulators.



16
17
18
# File 'lib/inquirex/definition.rb', line 16

def accumulators
  @accumulators
end

#actionsObject (readonly)

Returns the value of attribute actions.



16
17
18
# File 'lib/inquirex/definition.rb', line 16

def actions
  @actions
end

#allowed_domainsObject (readonly)

Returns the value of attribute allowed_domains.



16
17
18
# File 'lib/inquirex/definition.rb', line 16

def allowed_domains
  @allowed_domains
end

#idString? (readonly)

flow identifier (e.g. "tax-intake-2025")

Returns:

  • (String, nil)

    the current value of id



15
16
17
# File 'lib/inquirex/definition.rb', line 15

def id
  @id
end

#metaHash (readonly)

title, subtitle, brand info for the frontend

Returns:

  • (Hash)

    the current value of meta



15
16
17
# File 'lib/inquirex/definition.rb', line 15

def meta
  @meta
end

#start_step_idSymbol (readonly)

id of the first step in the flow

Returns:

  • (Symbol)

    the current value of start_step_id



15
16
17
# File 'lib/inquirex/definition.rb', line 15

def start_step_id
  @start_step_id
end

#stepsHash<Symbol, Node> (readonly)

frozen map of step id => node

Returns:

  • (Hash<Symbol, Node>)

    the current value of steps



15
16
17
# File 'lib/inquirex/definition.rb', line 15

def steps
  @steps
end

#versionString (readonly)

semver string (default: "1.0.0")

Returns:

  • (String)

    the current value of version



15
16
17
# File 'lib/inquirex/definition.rb', line 15

def version
  @version
end

Class Method Details

.from_h(hash) ⇒ Definition

Deserializes a Definition from a plain Hash.

Parameters:

  • hash (Hash)

    definition attributes (string or symbol keys)

Returns:



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/inquirex/definition.rb', line 128

def self.from_h(hash)
  id = hash["id"] || hash[:id]
  version = hash["version"] || hash[:version] || "1.0.0"
  meta = hash["meta"] || hash[:meta] || {}
  start = hash["start"] || hash[:start]
  steps_data = hash["steps"] || hash[:steps] || {}
  acc_data = hash["accumulators"] || hash[:accumulators] || {}
  actions_data = hash["actions"] || hash[:actions] || []
  domains = hash["allowed_domains"] || hash[:allowed_domains] || []

  nodes = steps_data.each_with_object({}) do |(step_id, step_hash), acc|
    sym_id = step_id.to_sym
    acc[sym_id] = Node.from_h(sym_id, step_hash)
  end

  accumulators = acc_data.each_with_object({}) do |(name, entry), h|
    sym = name.to_sym
    h[sym] = Accumulator.from_h(sym, entry)
  end

  actions = actions_data.map { |entry| Actions::Action.from_h(entry) }

  new(start_step_id: start,
    nodes:,
    id:,
    version:,
    meta:,
    accumulators:,
    actions:,
    allowed_domains: domains)
end

.from_json(json) ⇒ Definition

Deserializes a Definition from a JSON string.

Parameters:

  • json (String)

    JSON representation

Returns:



118
119
120
121
122
# File 'lib/inquirex/definition.rb', line 118

def self.from_json(json)
  from_h(JSON.parse(json))
rescue JSON::ParserError => e
  raise Errors::SerializationError, "Invalid JSON: #{e.message}"
end

Instance Method Details

#allowed_host?(host) ⇒ Boolean

Whether a host is covered by the allowed_domains declaration. "example.com" matches that host exactly; "*.example.com" matches any subdomain but not the apex. Matching is case-insensitive; an empty allowlist allows nothing.

Examples:

With allowed_domains ["*.example.com"]

definition.allowed_host?("api.example.com") # => true
definition.allowed_host?("API.EXAMPLE.COM") # => true (case-insensitive)
definition.allowed_host?("example.com")     # => false (wildcard excludes the apex)

Parameters:

  • host (String)

Returns:

  • (Boolean)


78
79
80
81
82
83
# File 'lib/inquirex/definition.rb', line 78

def allowed_host?(host)
  target = host.to_s.downcase
  @allowed_domains.any? do |entry|
    entry.start_with?("*.") ? target.end_with?(entry[1..]) : target == entry
  end
end

#start_stepNode

Returns the node for the start step.

Returns:

  • (Node)

    the node for the start step



50
51
52
# File 'lib/inquirex/definition.rb', line 50

def start_step
  step(@start_step_id)
end

#step(id) ⇒ Node

Returns the node for that step.

Parameters:

  • id (Symbol)

    step id

Returns:

  • (Node)

    the node for that step

Raises:



57
58
59
# File 'lib/inquirex/definition.rb', line 57

def step(id)
  @steps.fetch(id.to_sym) { raise Errors::UnknownStepError, "Unknown step: #{id.inspect}" }
end

#step_idsArray<Symbol>

Returns all step ids.

Returns:

  • (Array<Symbol>)

    all step ids



62
63
64
# File 'lib/inquirex/definition.rb', line 62

def step_ids
  @steps.keys
end

#to_hHash

Serializes to a plain Hash.

Returns:

  • (Hash)


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/inquirex/definition.rb', line 96

def to_h
  hash = {}
  hash["id"] = @id if @id
  hash["version"] = @version
  hash["meta"] = @meta unless @meta.empty?
  hash["allowed_domains"] = @allowed_domains unless @allowed_domains.empty?
  hash["start"] = @start_step_id.to_s
  unless @accumulators.empty?
    hash["accumulators"] = @accumulators.each_with_object({}) do |(name, acc), h|
      h[name.to_s] = acc.to_h
    end
  end
  hash["steps"] = @steps.transform_keys(&:to_s).transform_values(&:to_h)
  serializable_actions = @actions.select(&:serializable?)
  hash["actions"] = serializable_actions.map(&:to_h) unless serializable_actions.empty?
  hash
end

#to_jsonString

Serializes the definition to a JSON string. Lambdas (default procs, compute blocks) are silently stripped.

Returns:

  • (String)

    JSON representation



89
90
91
# File 'lib/inquirex/definition.rb', line 89

def to_json(*)
  JSON.generate(to_h)
end