Module: Ksef::FA3::Validator

Defined in:
lib/ksef/fa3/validator.rb

Overview

XSD validation against the bundled FA(3) schema (DESIGN.md §7.7 tier 2).

The schema cannot be compiled as it ships. Its single xsd:import points at an absolute http://crd.gov.pl/… URL, so libxml2 would try to fetch it over the network at validation time — unacceptable for an offline, deterministic test suite, and a silent hang or failure in an air-gapped deployment.

So the import is redirected to the bundled copy in memory. The pinned file on disk stays byte-for-byte identical, which is what keeps its recorded digest verifying (docs/REFERENCE.md §1).

Constant Summary collapse

SCHEMA_DIR =
File.expand_path("schema", __dir__)
MAIN_SCHEMA =
File.join(SCHEMA_DIR, "schemat_FA(3)_v1-0E.xsd")
BASE_SCHEMA_DIR =
File.join(SCHEMA_DIR, "bazowe")

Class Method Summary collapse

Class Method Details

.errors_for(xml) ⇒ Array<String>

Well-formedness is checked first, and separately. Nokogiri parses in recovery mode by default, so a document with an unclosed root or trailing text after it comes back as a usable tree — and that recovered tree validates clean against the schema. Until 2026-08-24 this method never looked at document.errors, so valid? returned true for XML that is not XML. KSeF's first admission rule is "a correct XML 1.0 document" (docs/REFERENCE.md §15.1), and it is the rule most likely to be violated by a broken template or a truncated upload.

Parameters:

  • xml (String, Nokogiri::XML::Document)

Returns:

  • (Array<String>) —

    validation messages, empty when the document is valid



41
42
43
44
45
46
47
# File 'lib/ksef/fa3/validator.rb', line 41

def errors_for(xml)
  document = xml.is_a?(Nokogiri::XML::Document) ? xml : Nokogiri::XML(xml)
  malformed = document.errors.map { |error| "not well-formed XML: #{error.message.strip}" }
  return malformed unless malformed.empty?

  schema.validate(document).map(&:message)
end

.schema ⇒ Nokogiri::XML::Schema

Compiled once and memoised: compiling the FA(3) schema is expensive, and a single client validating many invoices should pay for it once.

Returns:

  • (Nokogiri::XML::Schema)


27
28
29
# File 'lib/ksef/fa3/validator.rb', line 27

def schema
  @schema ||= Nokogiri::XML::Schema.from_document(rewritten_schema_document)
end

.valid?(xml) ⇒ Boolean

Returns:

  • (Boolean)


50
# File 'lib/ksef/fa3/validator.rb', line 50

def valid?(xml) = errors_for(xml).empty?

.validate!(xml) ⇒ Object

Raises:



53
54
55
56
57
58
59
# File 'lib/ksef/fa3/validator.rb', line 53

def validate!(xml)
  errors = errors_for(xml)
  return true if errors.empty?

  detail = errors.map { |e| "  - #{e}" }.join("\n")
  raise ValidationError, "Invoice does not conform to the FA(3) schema:\n#{detail}"
end