Module: Textus::Protocol::StoreEngine::Move

Defined in:
lib/textus/protocol/store_engine/move.rb

Defined Under Namespace

Classes: MoveResult

Class Method Summary collapse

Class Method Details

.append_audit(key, etag_before, etag_after, deps) ⇒ Object



90
91
92
93
94
95
96
# File 'lib/textus/protocol/store_engine/move.rb', line 90

def append_audit(key, etag_before, etag_after, deps)
  deps.audit_log.append(
    role: deps.call.role, verb: "key_mv", key:,
    etag_before:, etag_after:,
    correlation_id: deps.call.correlation_id
  )
end

.assert_source_exists!(key, path, deps) ⇒ Object

Raises:



50
51
52
53
54
# File 'lib/textus/protocol/store_engine/move.rb', line 50

def assert_source_exists!(key, path, deps)
  return if deps.file_system.exists?(path)

  raise UnknownKey.new(key, suggestions: deps.manifest.resolver.suggestions_for(key))
end

.assert_target_not_exists!(path) ⇒ Object

Raises:



56
57
58
59
60
# File 'lib/textus/protocol/store_engine/move.rb', line 56

def assert_target_not_exists!(path)
  return unless path && File.exist?(path)

  raise UsageError.new("target already exists")
end

.call(from_key:, to_key:, deps:, if_etag: nil, dry_run: false) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/textus/protocol/store_engine/move.rb', line 9

def call(from_key:, to_key:, deps:, if_etag: nil, dry_run: false)
  validate!(from_key, to_key)
  from_path, to_path, from_entry, to_entry = resolve_paths(from_key, to_key, deps)
  assert_source_exists!(from_key, from_path, deps)
  assert_target_not_exists!(to_path)
  check_cross_zone!(from_entry, to_entry)
  ensure_uid!(from_key, from_entry, from_path, deps)
  etag_before = Pipeline.check_etag(from_key, from_path, if_etag, deps.file_system)

  if dry_run
    return MoveResult.new(from_key:, to_key:, from_path:, to_path:,
                          dry_run: true, envelope: nil)
  end

  deps.file_system.mv(from_path, to_path)
  Pipeline.prune_parents(from_path, deps)
  rewrite_basename!(to_key, to_path, to_entry)
  etag_after = Value::Etag.for_file(to_path)
  envelope = deps.reader.read(key: to_key)
  append_audit(to_key, etag_before, etag_after, deps)
  reindex_entry(from_key, to_key, to_entry, envelope, deps)
  Pipeline.emit_event(deps, event_type: "entry.moved",
                            pending_key: from_key, emit_key: to_key,
                            emit_lane: to_entry.lane,
                            pending_keys: [from_key, to_key])
  MoveResult.new(from_key:, to_key:, from_path:, to_path:,
                 dry_run: false, envelope:)
end

.check_cross_zone!(from_entry, to_entry) ⇒ Object

Raises:



62
63
64
65
66
# File 'lib/textus/protocol/store_engine/move.rb', line 62

def check_cross_zone!(from_entry, to_entry)
  return if from_entry.lane == to_entry.lane

  raise UsageError.new("cross-zone moves forbidden (#{from_entry.lane}#{to_entry.lane})")
end

.ensure_uid!(_key, entry, path, deps) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/textus/protocol/store_engine/move.rb', line 68

def ensure_uid!(_key, entry, path, deps)
  return if Infra::Store::Envelope::Meta::NO_META_FORMATS.include?(entry.format)
  return unless path && deps.file_system.exists?(path)

  raw = deps.file_system.read(path)
  parsed = Format.for(entry.format).parse(raw, path:)
  meta = parsed["_meta"] || {}
  return if meta["uid"].is_a?(String) && !meta["uid"].empty?

  new_meta, = Infra::Store::Envelope::Meta.inject_all(meta, parsed["content"], {},
                                                      format: entry.format)
  bytes, = Format.for(entry.format).serialize_for_put(
    meta: new_meta, body: parsed["body"] || "", content: parsed["content"], path:,
  )
  deps.file_system.write(path, bytes)
end

.reindex_entry(from_key, to_key, to_entry, envelope, deps) ⇒ Object



98
99
100
101
102
103
104
105
# File 'lib/textus/protocol/store_engine/move.rb', line 98

def reindex_entry(from_key, to_key, to_entry, envelope, deps)
  deps.entry_index.remove(key: from_key)
  content = envelope.content ? envelope.content.to_json : envelope.body
  deps.entry_index.record(
    key: to_key, lane: to_entry.lane, format: to_entry.format,
    schema: to_entry.schema, content:
  )
end

.resolve_paths(from_key, to_key, deps) ⇒ Object



44
45
46
47
48
# File 'lib/textus/protocol/store_engine/move.rb', line 44

def resolve_paths(from_key, to_key, deps)
  from = deps.manifest.resolver.resolve(from_key)
  to   = deps.manifest.resolver.resolve(to_key)
  [from.path, to.path, from.entry, to.entry]
end

.rewrite_basename!(key, path, entry) ⇒ Object



85
86
87
88
# File 'lib/textus/protocol/store_engine/move.rb', line 85

def rewrite_basename!(key, path, entry)
  basename = key.split(".").last
  Format.for(entry.format).rewrite_name(path, basename)
end

.validate!(from_key, to_key) ⇒ Object

Raises:



38
39
40
41
42
# File 'lib/textus/protocol/store_engine/move.rb', line 38

def validate!(from_key, to_key)
  Manifest::Data.validate_key!(from_key)
  Manifest::Data.validate_key!(to_key)
  raise UsageError.new("old and new keys are identical") if from_key == to_key
end