Class: OcflTools::OcflVerify

Inherits:
Object
  • Object
show all
Defined in:
lib/ocfl_tools/ocfl_verify.rb

Overview

Class to verify that an instance of OcflObject or OcflInventory is composed of valid data and structures.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ocfl_object) ⇒ OcflVerify

Create a new OCFLVerify object, using an OcflTools::Ocflobject as source.

Parameters:



11
12
13
14
15
16
17
# File 'lib/ocfl_tools/ocfl_verify.rb', line 11

def initialize(ocfl_object)
  @my_victim = ocfl_object
  @my_results = OcflTools::OcflResults.new

  # check .respond_to? first for all expected methods.
  preflight
end

Instance Attribute Details

#my_resultsOcflTools::OcflResults (readonly)

Returns containing check results.

Returns:



7
8
9
# File 'lib/ocfl_tools/ocfl_verify.rb', line 7

def my_results
  @my_results
end

Instance Method Details

#check_allOcfltools::OcflResults

Performs all checks on the given object and reports results.

Returns:

  • (Ocfltools::OcflResults)

    of results.



27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/ocfl_tools/ocfl_verify.rb', line 27

def check_all
  # Duck-typing the heck out of this, assuming @my_victim will respond to ocflobject methods.
  check_id
  check_type
  check_head
  check_fixity
  check_manifest
  check_versions
  crosscheck_digests
  check_digestAlgorithm
  @my_results
end

#check_digestAlgorithmOcfltools::OcflResults

Checks OCFL Object for valid value in the digestAlgorithm attribute.

Returns:

  • (Ocfltools::OcflResults)

    of results.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/ocfl_tools/ocfl_verify.rb', line 105

def check_digestAlgorithm
  # If there's no digestAlgorithm set in the inventory, that's a showstopper.
  if @my_victim.digestAlgorithm == nil
    @my_results.error('E222', 'check_digestAlgorithm', "Algorithm cannot be nil")
    return @my_results
  end

  # must be one of sha256 or sha512
  if @my_victim.digestAlgorithm.downcase == 'sha256'
    @my_results.ok('O200', 'check_digestAlgorithm', 'OCFL 3.5.1 Inventory Algorithm is OK.')
    @my_results.info('I220', 'check_digestAlgorithm', "OCFL 3.5.1 #{@my_victim.digestAlgorithm.downcase} is a supported digest algorithm.")
    @my_results.warn('W220', 'check_digestAlgorithm', "OCFL 3.5.1 #{@my_victim.digestAlgorithm.downcase} SHOULD be Sha512.")
  elsif @my_victim.digestAlgorithm.downcase == 'sha512'
    @my_results.ok('O200', 'check_digestAlgorithm', 'OCFL 3.5.1 Inventory Algorithm is OK.')
    @my_results.info('I220', 'check_digestAlgorithm', "OCFL 3.5.1 #{@my_victim.digestAlgorithm.downcase} is a supported digest algorithm.")
  else
    @my_results.error('E223', 'check_digestAlgorithm', "OCFL 3.5.1 Algorithm #{@my_victim.digestAlgorithm} is not valid for OCFL use.")
  end
  @my_results
end

#check_fixityOcfltools::OcflResults

Checks OCFL Object for a well-formed fixity block, if present. We do not compute fixity here; only check existence.

Returns:

  • (Ocfltools::OcflResults)

    of results.



212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/ocfl_tools/ocfl_verify.rb', line 212

def check_fixity
  # If present, should have at least 1 sub-key and 1 value.
  errors = nil
  unless @my_victim.fixity.empty?
    @my_results.info('I111', 'check_fixity', 'Fixity block is present.')
  end
  # Set OcflTools.config.fixity_algorithms for what to look for.
  @my_victim.fixity.each do |algorithm, _digest|
    unless OcflTools.config.fixity_algorithms.include? algorithm
      @my_results.error('E111', 'check_fixity', "Fixity block contains unsupported algorithm #{algorithm}")
      errors = true
    end
  end

  if errors.nil? && !@my_victim.fixity.empty?
    @my_results.ok('O111', 'check_fixity', 'Fixity block is present and contains valid algorithms.')
  end

  @my_results
end

#check_headOcfltools::OcflResults

Checks OCFL Object for valid value in the head attribute.

Returns:

  • (Ocfltools::OcflResults)

    of results.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/ocfl_tools/ocfl_verify.rb', line 67

def check_head
  case @my_victim.head
  when nil
    @my_results.error('E212', 'check_head', 'OCFL 3.5.1 @head cannot be nil')
  when Integer
    @my_results.error('E213', 'check_head', 'OCFL 3.5.1 @head cannot be an Integer')
  when String
    version = OcflTools::Utils.version_string_to_int(@my_victim.head)
    target_version = @my_victim.version_id_list.max
    if version == target_version
      @my_results.ok('O200', 'check_head', 'OCFL 3.5.1 Inventory Head is OK.')
      @my_results.info('I200', 'check_head', "OCFL 3.5.1 Inventory Head version #{version} matches highest version in versions.")
    else
      @my_results.error('E214', 'check_head', "OCFL 3.5.1 Inventory Head version #{version} does not match expected version #{target_version}")
    end
  else
    # default case error
    @my_results.error('E911', 'check_head', 'An unknown error has occurred.')
  end
  @my_results
end

#check_idOcfltools::OcflResults

Checks OCFL Object for valid value in the id attribute. Id value MUST be present and SHOULD be a URI.

Returns:

  • (Ocfltools::OcflResults)

    of results.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/ocfl_tools/ocfl_verify.rb', line 43

def check_id
  case @my_victim.id
    when nil
      @my_results.error('E202', 'check_id', 'OCFL 3.5.1 Object ID cannot be nil')
      return @my_results
    when 0
      @my_results.error('E201', 'check_id', 'OCFL 3.5.1 Object ID cannot be 0 length')
      return @my_results
    when !String
      @my_results.error('E201', 'check_id', 'OCFL 3.5.1 Object ID must be a string.')
      return @my_results
  end

  if @my_victim.id =~ /\A#{URI::regexp}\z/
    @my_results.ok('O200', 'check_id', 'OCFL 3.5.1 Inventory ID is OK.')
    return @my_results
  else
    @my_results.warn('W201', 'check_id', 'OCFL 3.5.1 Inventory ID present, but does not appear to be a URI.')
    return @my_results
  end
end

#check_manifestOcfltools::OcflResults

Checks OCFL Object for a well-formed manifest block.

Returns:

  • (Ocfltools::OcflResults)

    of results.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/ocfl_tools/ocfl_verify.rb', line 128

def check_manifest
  # Should pass digest cross_check.
  # can be null if it passes cross_check? (empty inventories are valid, but warn)
  # There MUST be a block called 'manifests'
  errors = nil
  if @my_victim.manifest.nil?
    @my_results.error('E250', 'check_manifest', 'OCFL 3.5.2 there MUST be a manifest block.')
    errors = true
  elsif @my_victim.manifest == {}
    @my_results.error('E251', 'check_manifest', 'OCFL 3.5.2 manifest block cannot be empty.')
    errors = true
  end

  # TODO: Should check that it's a hash of digests and filepaths somehow...?
  # Get digest Algo type, use that to get key length.
  # check all keys in manifest to make sure they're all that length.

  if errors.nil?
    @my_results.ok('O200', 'check_manifest', 'OCFL 3.5.2 Inventory Manifest syntax is OK.')
  end

  @my_results
end

#check_typeOcfltools::OcflResults

Checks OCFL Object for valid value in the type attribute.

Returns:

  • (Ocfltools::OcflResults)

    of results.



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/ocfl_tools/ocfl_verify.rb', line 91

def check_type
  case @my_victim.type
  when nil
    @my_results.error('E230', 'check_type', 'OCFL 3.5.1 Required OCFL key type not found.')
  when 'https://ocfl.io/1.0/spec/#inventory'
    @my_results.ok('O200', 'check_type', 'OCFL 3.5.1 Inventory Type is OK.')
  else
    @my_results.error('E231', 'check_type', 'OCFL 3.5.1 Required OCFL key type does not match expected value.')
  end
  @my_results
end

#check_versionsOcfltools::OcflResults

Checks OCFL Object for a well-formed versions block.

Returns:

  • (Ocfltools::OcflResults)

    of results.



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/ocfl_tools/ocfl_verify.rb', line 154

def check_versions
  version_count   = @my_victim.version_id_list.length
  highest_version = @my_victim.version_id_list.max
  my_versions     = @my_victim.version_id_list.sort

  @version_check = nil
  if version_count != highest_version
    @my_results.error('E014', 'check_versions', "OCFL 3.5.3 Found #{version_count} versions, but highest version is #{highest_version}")
    @version_check = true
  elsif version_count == highest_version
    @my_results.ok('O200', 'check_versions', "OCFL 3.5.3 Found #{version_count} versions, highest version is #{highest_version}")
  end
  # should be contiguous version numbers starting at 1.
  count = 0
  until count == highest_version
    # (count - 1) is a proxy for the index in @my_victim.version_id_list.sort
    count += 1
    if count != my_versions[count - 1]
      @my_results.error('E015', 'check_versions', "OCFL 3.5.3 Expected version sequence not found. Expected version #{count}, found version #{my_versions[count]}.")
      @version_check = true
    end
  end
  # We do NOT need to check the @versions.keys here for 'v0001', etc.
  # That's already been done when we looked at version_id_list and
  # checked for contiguous version numbers in my_versions.

  @my_victim.versions.each do |version, hash|
    %w[created message user state].each do |key|
      if hash.key?(key) == false
        @my_results.error('E016', 'check_versions', "OCFL 3.5.3.1 version #{version} is missing #{key} block.")
        @version_check = true
        next
      end # key is present, does it conform?

      case key
        when 'created'
          check_version_created(hash['created'], version)
        when 'user'
          check_version_user(hash['user'], version)
        when 'state'
          check_version_state(hash['state'], version)
        when 'message'
          check_version_message(hash['message'], version)
        else
          @my_results.error('E111', 'check_versions', "OCFL 3.5.3.1 version #{version} contains unknown key #{key} block.")
          @version_check = true
      end
    end
  end

  if @version_check.nil?
    @my_results.ok('O200', 'check_versions', 'OCFL 3.5.3.1 version syntax is OK.')
  end
  @my_results
end

#crosscheck_digestsOcfltools::OcflResults

Checks the contents of the manifest block against the files and digests in the versions block to verify all files necessary to re-constitute the object at any version are correctly referenced in the OCFL Object.

Returns:

  • (Ocfltools::OcflResults)

    of results.



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/ocfl_tools/ocfl_verify.rb', line 236

def crosscheck_digests
  # requires values in @versions and @manifest.
  # verifies that every digest in @versions can be found in @manifest.
  errors = nil
  my_checksums = []

  @my_victim.versions.each do |version, block|
    if !block.is_a?(Hash)
      @my_results.error('E111', 'crosscheck_digests', "version #{version} block is wrong type.")
      next
    end
    version_digests = block['state']
    if !version_digests.is_a?(Hash)
      @my_results.error('E111', 'crosscheck_digests', "version #{version} state block is wrong type.")
      next
    end
    version_digests.each_key { |k| my_checksums << k }
  end

  unique_checksums = my_checksums.uniq

  # First check; there should be the same number of entries on both sides.
  if unique_checksums.length != @my_victim.manifest.length
    @my_results.error('E050', 'crosscheck_digests', "OCFL 3.5.3.1 Digests missing! #{unique_checksums.length} digests in versions vs. #{@my_victim.manifest.length} digests in manifest.")
    errors = true
  end

  # Second check; each entry in unique_checksums should have a match in @manifest.
  unique_checksums.each do |checksum|
    if @my_victim.manifest.member?(checksum) == false
      @my_results.error('E051', 'crosscheck_digests', "OCFL 3.5.3.1 Checksum #{checksum} not found in manifest!")
      errors = true
    end
  end

  if errors.nil?
    @my_results.ok('O200', 'crosscheck_digests', 'OCFL 3.5.3.1 Digests are OK.')
  end
  @my_results
end

#preflightBoolean

Verifies that the object passed to this class at instantiation responds to the expected methods and attributes. Raises an exception on failure.

Returns:

  • (Boolean)

    true



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/ocfl_tools/ocfl_verify.rb', line 280

def preflight
  # check for expected instance_variables with .instance_variable_defined?(@some_var)
  ['@id', '@head', '@type', '@digestAlgorithm', '@contentDirectory', '@manifest', '@versions', '@fixity'].each do |var|
    unless @my_victim.instance_variable_defined?(var)
      raise "Object does not have instance var #{var} defined"
    end
  end

  # check for all methods we need to validate OCFL structure
  %w[get_files get_current_files get_state version_id_list get_digest].each do |mthd|
    unless @my_victim.respond_to?(mthd)
      raise "Object does not respond to #{mthd}"
    end
  end
end

#resultsOcflTools::OcflResults

against this object.

Returns:



21
22
23
# File 'lib/ocfl_tools/ocfl_verify.rb', line 21

def results
  @my_results
end