Class: SpecGuard::RSpec::Schema

Inherits:
Object
  • Object
show all
Defined in:
lib/specguard/rspec/schema.rb

Overview

The OpenTestIntent v1 schema, loaded and applied.

Why loading failure is exit 2 and not exit 1

The exit contract (SPGD-12 §1) spends 1 on "an annotation is malformed". If a packaging accident leaves the vendored schema out of the gem, the obvious Ruby implementation lets the exception escape and Ruby exits 1 — so CI reports a malformed annotation and a developer goes hunting for a bad annotation that does not exist. The reference tool already ruled on this:

except (OSError, json.JSONDecodeError) as exc:
    print("error: could not load schema %s: %s" % (SCHEMA_PATH, exc), file=sys.stderr)
    return 2
# open-test-intent, bin/validate-intent:858-862

Hence SchemaError: every way loading can fail is caught here and retyped, so the CLI can map the whole class of them to 2 without inspecting exception classes from three different libraries.

The CLI loads this before it scans anything, so a broken schema can never produce a run that reports "0 malformed" having validated nothing.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(document:, path: SCHEMA_PATH, renderer: ViolationRenderer.new) ⇒ Schema

Returns a new instance of Schema.



59
60
61
62
63
64
65
# File 'lib/specguard/rspec/schema.rb', line 59

def initialize(document:, path: SCHEMA_PATH, renderer: ViolationRenderer.new)
  @document = document
  @path = path
  @renderer = renderer
  @schemer = JSONSchemer.schema(document)
  reject_unusable_schema!
end

Instance Attribute Details

#documentObject (readonly)

Returns the value of attribute document.



57
58
59
# File 'lib/specguard/rspec/schema.rb', line 57

def document
  @document
end

#pathObject (readonly)

Returns the value of attribute path.



57
58
59
# File 'lib/specguard/rspec/schema.rb', line 57

def path
  @path
end

Class Method Details

.load(path = SCHEMA_PATH) ⇒ Schema

Parameters:

  • path (String) (defaults to: SCHEMA_PATH)

    the vendored schema

Returns:

Raises:

  • (SchemaError)

    if it cannot be read, parsed, or compiled



45
46
47
48
49
50
51
52
53
54
55
# File 'lib/specguard/rspec/schema.rb', line 45

def self.load(path = SCHEMA_PATH)
  document = ::JSON.parse(File.read(path, encoding: "UTF-8"))
  new(document: document, path: path)
rescue StandardError => e
  # Intentionally broad. Reading can raise SystemCallError/IOError,
  # parsing JSON::ParserError, and compiling or meta-validating whatever
  # json_schemer decides an unusable schema document deserves. All of
  # them mean the same thing to the caller, and all of them must be 2
  # rather than an uncaught exception's 1.
  raise SchemaError, "could not load schema #{path}: #{e.message}"
end

Instance Method Details

#violations(intent) ⇒ Array<String>

The validator decides the verdict; the renderer only decides the wording. Deriving "is this annotation valid?" from "could we phrase a sentence about it?" would make an unphrasable violation and a clean annotation the same state — and under Linter that state is a green run, which is this project's signature vacuous green (KB SPGD-78) arriving through the one door the whole rendering strategy exists to close. The gemspec pins ~> 2.5, which admits 2.6 and beyond; the renderer is built from structured fields precisely so a bump degrades to wrong-looking text rather than to silence, and this is what keeps that promise true for error shapes it has never seen.

Parameters:

  • intent (Object)

    one parsed annotation

Returns:

  • (Array<String>)

    reason lines in the reference tool's grammar and order; empty means the annotation is valid



81
82
83
84
85
86
87
# File 'lib/specguard/rspec/schema.rb', line 81

def violations(intent)
  errors = @schemer.validate(intent).to_a
  return [] if errors.empty?

  rendered = @renderer.render(errors, intent)
  rendered.empty? ? errors.map { |error| unrenderable_reason(error) } : rendered
end