Class: Ksef::UPO::Document

Inherits:
Data
  • Object
show all
Defined in:
lib/ksef/upo/document.rb

Overview

A retrieved UPO, held as the exact bytes received (docs/REFERENCE.md §12).

xml is deliberately a raw String and not a parsed document. The Ministry's XAdES signature covers the octets, so anything that re-serialises them — even a lossless round-trip through an XML library — risks producing a document that no longer verifies. Parse a copy if you need to read it; archive this.

source records where the bytes came from, because the two routes have different properties worth knowing after the fact: :storage is the unmetered pre-signed link (§14.2), :api the metered fallback.

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#published_hash ⇒ Object (readonly)

Returns the value of attribute published_hash

Returns:

  • (Object) —

    the current value of published_hash



15
16
17
# File 'lib/ksef/upo/document.rb', line 15

def published_hash
  @published_hash
end

#source ⇒ Object (readonly)

Returns the value of attribute source

Returns:

  • (Object) —

    the current value of source



15
16
17
# File 'lib/ksef/upo/document.rb', line 15

def source
  @source
end

#xml ⇒ Object (readonly)

Returns the value of attribute xml

Returns:

  • (Object) —

    the current value of xml



15
16
17
# File 'lib/ksef/upo/document.rb', line 15

def xml
  @xml
end

Instance Method Details

#inspect ⇒ Object



68
69
70
71
# File 'lib/ksef/upo/document.rb', line 68

def inspect
  state = verifiable? ? verified?.to_s : "unverifiable"
  "#<data Ksef::UPO::Document size=#{size} source=#{source.inspect} verified=#{state}>"
end

#sha256 ⇒ Object

SHA-256 of the bytes held, Base64 — the same form x-ms-meta-hash uses.



17
# File 'lib/ksef/upo/document.rb', line 17

def sha256 = Ksef::Crypto::Digest.of(xml).base64

#size ⇒ Object



19
# File 'lib/ksef/upo/document.rb', line 19

def size = xml.bytesize

#to_s ⇒ Object

Deliberately not the document: a UPO is a few kilobytes of XML, and putting it in a log line or an exception message helps nobody. DESIGN.md §4.5 forbids full invoice payloads at default log level, and the same reasoning applies here.



66
# File 'lib/ksef/upo/document.rb', line 66

def to_s = "#<Ksef::UPO::Document #{size} bytes from #{source}>"

#validate ⇒ Validation

Checks the document against the bundled schema — a diagnostic, never a gate (§14.3). Archive the bytes regardless of what this says; see Validator for why there is no raising counterpart.

Returns:



51
# File 'lib/ksef/upo/document.rb', line 51

def validate = Validator.validate(xml)

#verifiable? ⇒ Boolean

Whether there is anything to check against at all. The pre-signed storage link publishes x-ms-meta-hash; the metered API route publishes nothing.

Split from #verified? rather than folded into it as a third state: "no hash was published" and "the hash did not match" call for completely different responses, and a predicate that answers both with one value invites treating an unverifiable document as a corrupt one.

Returns:

  • (Boolean)


28
# File 'lib/ksef/upo/document.rb', line 28

def verifiable? = !published_hash.nil?

#verified? ⇒ Boolean

True only when a hash was published and the bytes match it. False for an unverifiable document too — ask #verifiable? to tell the two apart.

Returns:

  • (Boolean)


32
# File 'lib/ksef/upo/document.rb', line 32

def verified? = verifiable? && sha256 == published_hash

#verify! ⇒ self

Returns:

  • (self)

Raises:



36
37
38
39
40
41
42
43
44
# File 'lib/ksef/upo/document.rb', line 36

def verify!
  return self unless verifiable? && !verified?

  raise IntegrityError,
        "The UPO fetched from #{source} does not match the hash the server published: " \
        "expected #{published_hash}, got #{sha256} over #{size} bytes. This is a corrupted " \
        "transfer, not a bad request — fetch it again. Do not archive these bytes as proof " \
        "of receipt (docs/REFERENCE.md §14.2)."
end

#write(path) ⇒ Integer

Writes the bytes untouched. Binary mode on purpose: this must not pick up a newline translation or a re-encode on the way to disk, or the archived file stops matching what the Ministry signed.

Parameters:

  • path (String)

Returns:

  • (Integer) —

    bytes written



59
60
61
# File 'lib/ksef/upo/document.rb', line 59

def write(path)
  File.binwrite(path, xml)
end