From 5ab98756512db11829a010b133f90f8fd2bc035d Mon Sep 17 00:00:00 2001 From: Johnathan Martin Date: Wed, 8 Nov 2023 00:16:08 -0800 Subject: [PATCH] switch LanguageTagValidator to operate on DRO::TYPES so that it's invoked automatically when building item models --- .../validators/language_tag_validator.rb | 44 +++++++++++--- .../validators/language_tag_validator_spec.rb | 58 +++++++++++++------ 2 files changed, 74 insertions(+), 28 deletions(-) diff --git a/lib/cocina/models/validators/language_tag_validator.rb b/lib/cocina/models/validators/language_tag_validator.rb index 866e7fbc..371700a2 100644 --- a/lib/cocina/models/validators/language_tag_validator.rb +++ b/lib/cocina/models/validators/language_tag_validator.rb @@ -17,10 +17,10 @@ def initialize(clazz, attributes) def validate return unless meets_preconditions? - return if valid_language_tag? + return if invalid_files.empty? - raise ValidationError, 'The provided language tag is not valid according to RFC 4646: ' \ - "#{attributes[:languageTag]}" + raise ValidationError, 'Some files have invalid language tags according to RFC 4646: ' \ + "#{invalid_filenames_with_language_tags.join(', ')}" end private @@ -28,19 +28,45 @@ def validate attr_reader :clazz, :attributes def meets_preconditions? - file? && attributes[:languageTag].present? + dro? end - def file? - (clazz::TYPES & File::TYPES).any? + def dro? + (clazz::TYPES & DRO::TYPES).any? rescue NameError false end - def valid_language_tag? - parsed_tag = I18n::Locale::Tag::Rfc4646.tag(attributes[:languageTag]) + def valid_language_tag?(file) + # I18n::Locale::Tag::Rfc4646.tag will return an instance of I18n::Locale::Tag::Rfc4646 (with fields like language, script, + # region) for strings that can be parsed according to RFC 4646, and nil for strings that do not conform to the spec. + I18n::Locale::Tag::Rfc4646.tag(file[:languageTag]).present? + end + + def invalid_files + @invalid_files ||= language_tag_files.reject { |file| valid_language_tag?(file) } + end + + def invalid_filenames_with_language_tags + invalid_files.map { |invalid_file| "#{invalid_file[:filename] || invalid_file[:label]} (#{invalid_file[:languageTag]})" } + end + + def language_tag_files + files.select { |file| file[:languageTag].present? } + end + + def files + [].tap do |files| + next if attributes.dig(:structural, :contains).nil? + + attributes[:structural][:contains].each do |fileset| + next if fileset.dig(:structural, :contains).nil? - parsed_tag.present? && parsed_tag.is_a?(I18n::Locale::Tag::Rfc4646) + fileset[:structural][:contains].each do |file| + files << file + end + end + end end end end diff --git a/spec/cocina/models/validators/language_tag_validator_spec.rb b/spec/cocina/models/validators/language_tag_validator_spec.rb index 99de8083..ff0cdf25 100644 --- a/spec/cocina/models/validators/language_tag_validator_spec.rb +++ b/spec/cocina/models/validators/language_tag_validator_spec.rb @@ -3,7 +3,27 @@ require 'spec_helper' RSpec.describe Cocina::Models::Validators::LanguageTagValidator do - let(:validate) { described_class.validate(clazz, props) } + let(:validate) { described_class.validate(clazz, dro_props) } + + let(:dro_props) do + { + type: Cocina::Models::ObjectType.book, + access: { view: 'dark', download: 'none' }, + structural: { + contains: [ + { + externalIdentifier: 'bc123df4567_1', + label: 'Fileset 1', + type: Cocina::Models::FileSetType.file, + version: 1, + structural: { + contains: [props] + } + } + ] + } + } + end let(:props) { file_props } @@ -26,16 +46,16 @@ end context 'with no value for languageTag specified' do - context 'with a File' do - let(:clazz) { Cocina::Models::File } + context 'with a DRO' do + let(:clazz) { Cocina::Models::DRO } it 'does not raise' do expect { validate }.not_to raise_error end end - context 'with a RequestFile' do - let(:clazz) { Cocina::Models::RequestFile } + context 'with a RequestDRO' do + let(:clazz) { Cocina::Models::RequestDRO } it 'does not raise' do expect { validate }.not_to raise_error @@ -53,16 +73,16 @@ context 'with a recognized language, script, and region' do let(:language_tag) { 'ru-Cyrl-RU' } - context 'with a File' do - let(:clazz) { Cocina::Models::File } + context 'with a DRO' do + let(:clazz) { Cocina::Models::DRO } it 'does not raise' do expect { validate }.not_to raise_error end end - context 'with a RequestFile' do - let(:clazz) { Cocina::Models::RequestFile } + context 'with a RequestDRO' do + let(:clazz) { Cocina::Models::RequestDRO } it 'does not raise' do expect { validate }.not_to raise_error @@ -73,16 +93,16 @@ context 'with an unrecognized language, script, and region' do let(:language_tag) { 'foo-Barr-BZ' } # still conforms to the expected format of BCP 47/RFC 4646, should parse - context 'with a File' do - let(:clazz) { Cocina::Models::File } + context 'with a DRO' do + let(:clazz) { Cocina::Models::DRO } it 'does not raise' do expect { validate }.not_to raise_error end end - context 'with a RequestFile' do - let(:clazz) { Cocina::Models::RequestFile } + context 'with a RequestDRO' do + let(:clazz) { Cocina::Models::RequestDRO } it 'does not raise' do expect { validate }.not_to raise_error @@ -98,19 +118,19 @@ end end - context 'with a File' do - let(:clazz) { Cocina::Models::File } + context 'with a DRO' do + let(:clazz) { Cocina::Models::DRO } it 'raises a validation error' do - expect { validate }.to raise_error(Cocina::Models::ValidationError, 'The provided language tag is not valid according to RFC 4646: fooooooooooooo') + expect { validate }.to raise_error(Cocina::Models::ValidationError, 'Some files have invalid language tags according to RFC 4646: page1.txt (fooooooooooooo)') end end - context 'with a RequestFile' do - let(:clazz) { Cocina::Models::RequestFile } + context 'with a RequestDRO' do + let(:clazz) { Cocina::Models::RequestDRO } it 'raises a validation error' do - expect { validate }.to raise_error(Cocina::Models::ValidationError, 'The provided language tag is not valid according to RFC 4646: fooooooooooooo') + expect { validate }.to raise_error(Cocina::Models::ValidationError, 'Some files have invalid language tags according to RFC 4646: page1.txt (fooooooooooooo)') end end end