Class: Textus::Store::Writer

Inherits:
Object
  • Object
show all
Defined in:
lib/textus/store/writer.rb

Defined Under Namespace

Classes: Payload

Instance Method Summary collapse

Constructor Details

#initialize(store) ⇒ Writer

Returns a new instance of Writer.



8
9
10
11
12
# File 'lib/textus/store/writer.rb', line 8

def initialize(store)
  @store = store
  @manifest = store.manifest
  @reader = store.reader
end

Instance Method Details

#delete_envelope_from_disk(key, ctx:, if_etag: nil) ⇒ Object

Pure I/O: resolve path, validate etag, delete from disk, audit. No permission check and no event firing — those are handled by the caller (Application::Writes::Delete).

Raises:



86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/textus/store/writer.rb', line 86

def delete_envelope_from_disk(key, ctx:, if_etag: nil)
  _, path, = @manifest.resolve(key)
  raise UnknownKey.new(key, suggestions: @manifest.suggestions_for(key)) unless File.exist?(path)

  etag_before = Etag.for_file(path)
  raise EtagMismatch.new(key, if_etag, etag_before) if if_etag && if_etag != etag_before

  File.delete(path)
  @store.audit_log.append(
    role: ctx.role, verb: "delete", key: key,
    etag_before: etag_before, etag_after: nil,
    extras: ctx.correlation_id ? { "correlation_id" => ctx.correlation_id } : nil
  )
end

#enforce_name_match!(path, meta, format) ⇒ Object



72
73
74
# File 'lib/textus/store/writer.rb', line 72

def enforce_name_match!(path, meta, format)
  Textus::Entry.for_format(format).enforce_name_match!(path, meta)
end

#ensure_uid(format, meta, content, existing_uid) ⇒ Object



68
69
70
# File 'lib/textus/store/writer.rb', line 68

def ensure_uid(format, meta, content, existing_uid)
  Textus::Entry.for_format(format).inject_uid(meta, content, existing_uid)
end

#existing_uid_for(mentry, path) ⇒ Object



58
59
60
61
62
63
64
65
66
# File 'lib/textus/store/writer.rb', line 58

def existing_uid_for(mentry, path)
  return nil unless File.exist?(path)

  raw = File.binread(path)
  parsed = Entry.for_format(mentry.format).parse(raw, path: path)
  Envelope.extract_uid(parsed["_meta"])
rescue StandardError
  nil
end

#serialize_for_put(mentry:, path:, strategy:, meta:, body:, content:) ⇒ Object



76
77
78
79
80
81
# File 'lib/textus/store/writer.rb', line 76

def serialize_for_put(mentry:, path:, strategy:, meta:, body:, content:)
  _ = strategy
  Textus::Entry.for_format(mentry.format).serialize_for_put(
    meta: meta, body: body, content: content, path: path,
  )
end

#write_envelope_to_disk(key, mentry:, payload:, ctx:, if_etag: nil) ⇒ Object

Pure I/O: validate, serialize, etag-check, write to disk, audit. No permission check and no event firing — those are handled by the caller (Application::Writes::Put).

Raises:



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/textus/store/writer.rb', line 17

def write_envelope_to_disk(key, mentry:, payload:, ctx:, if_etag: nil)
  _, path, = @manifest.resolve(key)

  meta = payload.meta || {}
  strategy = Entry.for_format(mentry.format)

  existing_uid = existing_uid_for(mentry, path)
  meta, content = ensure_uid(mentry.format, meta, payload.content, existing_uid)

  bytes, eff_meta, eff_body, eff_content = serialize_for_put(
    mentry: mentry, path: path, strategy: strategy,
    meta: meta, body: payload.body, content: content
  )

  enforce_name_match!(path, eff_meta, mentry.format)

  schema = @store.schema_for(mentry.schema)
  if schema
    Entry.for_format(mentry.format).validate_against(
      schema,
      { "_meta" => eff_meta, "content" => eff_content },
    )
  end

  etag_before = File.exist?(path) ? Etag.for_file(path) : nil
  raise EtagMismatch.new(key, if_etag, etag_before) if if_etag && (etag_before != if_etag)

  FileUtils.mkdir_p(File.dirname(path))
  File.binwrite(path, bytes)
  etag_after = Etag.for_bytes(bytes)
  @store.audit_log.append(
    role: ctx.role, verb: "put", key: key,
    etag_before: etag_before, etag_after: etag_after,
    extras: ctx.correlation_id ? { "correlation_id" => ctx.correlation_id } : nil
  )
  Envelope.build(
    key: key, mentry: mentry, path: path,
    meta: eff_meta, body: eff_body, etag: etag_after, content: eff_content
  )
end