Class: Textus::Application::Writes::EnvelopeIO

Inherits:
Object
  • Object
show all
Defined in:
lib/textus/application/writes/envelope_io.rb

Overview

Owns the write pipeline (validate, serialize, etag-check, write, audit) extracted from Store::Writer. Talks to ports (FileStore, Schemas, AuditLog, Manifest) instead of File/FileUtils and Store directly.

No permission check, no event firing — those belong to the caller (Application::Writes::Put / ::Delete).

Defined Under Namespace

Classes: Payload

Instance Method Summary collapse

Constructor Details

#initialize(file_store:, manifest:, schemas:, audit_log:, ctx:) ⇒ EnvelopeIO

Returns a new instance of EnvelopeIO.



13
14
15
16
17
18
19
# File 'lib/textus/application/writes/envelope_io.rb', line 13

def initialize(file_store:, manifest:, schemas:, audit_log:, ctx:)
  @file_store = file_store
  @manifest   = manifest
  @schemas    = schemas
  @audit_log  = audit_log
  @ctx        = ctx
end

Instance Method Details

#delete(key, mentry:, if_etag: nil) ⇒ Object

Raises:



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/textus/application/writes/envelope_io.rb', line 61

def delete(key, mentry:, if_etag: nil)
  _ = mentry
  path = @manifest.resolve(key).path
  raise UnknownKey.new(key, suggestions: @manifest.suggestions_for(key)) unless @file_store.exists?(path)

  etag_before = @file_store.etag(path)
  raise EtagMismatch.new(key, if_etag, etag_before) if if_etag && if_etag != etag_before

  @file_store.delete(path)
  @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

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

Raises:



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
57
58
59
# File 'lib/textus/application/writes/envelope_io.rb', line 21

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

  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 = @schemas.fetch_or_nil(mentry.schema)
  if schema
    Entry.for_format(mentry.format).validate_against(
      schema,
      { "_meta" => eff_meta, "content" => eff_content },
    )
  end

  etag_before = @file_store.exists?(path) ? @file_store.etag(path) : nil
  raise EtagMismatch.new(key, if_etag, etag_before) if if_etag && (etag_before != if_etag)

  @file_store.write(path, bytes)
  etag_after = Etag.for_bytes(bytes)
  @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