diff --git a/index.yaml b/index.yaml index c5358245..43b87e05 100644 --- a/index.yaml +++ b/index.yaml @@ -6,8 +6,8 @@ defs: AU: ['au.yaml'] BE_FR: ['be_fr.yaml'] BE_NL: ['be_nl.yaml'] - BR: ['br.yaml'] BG: ['bg.yaml'] + BR: ['br.yaml'] BY: ['by.yaml'] CA: ['ca.yaml', 'northamericainformal.yaml'] CH: ['ch.yaml'] @@ -17,43 +17,40 @@ defs: CR: ['cr.yaml'] CY: ['cy.yaml'] CZ: ['cz.yaml'] - DK: ['dk.yaml'] DE: ['de.yaml'] - ECBTARGET: ['ecbtarget.yaml'] + DK: ['dk.yaml'] EE: ['ee.yaml'] ES: ['es.yaml'] - FederalReserve: ['federalreserve.yaml'] - FederalReserveBanks: ['federalreservebanks.yaml'] - FedEx: ['fedex.yaml'] FI: ['fi.yaml'] FR: ['fr.yaml'] GB: ['gb.yaml'] GE: ['ge.yaml'] GR: ['gr.yaml'] - HR: ['hr.yaml'] HK: ['hk.yaml'] + HR: ['hr.yaml'] HU: ['hu.yaml'] IE: ['ie.yaml'] IL: ['il.yaml'] IN: ['in.yaml'] IS: ['is.yaml'] IT: ['it.yaml'] + JP: ['jp.yaml'] KE: ['ke.yaml'] KR: ['kr.yaml'] KZ: ['kz.yaml'] LI: ['li.yaml'] LT: ['lt.yaml'] + LU: ['lu.yaml'] LV: ['lv.yaml'] MA: ['ma.yaml'] MC: ['mc.yaml'] - MT_MT: ['mt_mt.yaml'] MT_EN: ['mt_en.yaml'] + MT_MT: ['mt_mt.yaml'] MX: ['mx.yaml', 'northamericainformal.yaml'] - NERC: ['nerc.yaml'] + MY: ['my.yaml'] + NG: ['ng.yaml'] NL: ['nl.yaml'] - LU: ['lu.yaml'] "NO": ['no.yaml'] - NYSE: ['nyse.yaml'] NZ: ['nz.yaml'] PE: ['pe.yaml'] PH: ['ph.yaml'] @@ -64,24 +61,29 @@ defs: RS_LA: ['rs_la.yaml'] RU: ['ru.yaml'] SE: ['se.yaml'] + SG: ['sg.yaml'] + SI: ['si.yaml'] + SK: ['sk.yaml'] + TH: ['th.yaml'] TN: ['tn.yaml'] TR: ['tr.yaml'] - TSX: ['tsx.yaml'] UA: ['ua.yaml'] US: ['us.yaml', 'northamericainformal.yaml'] - UnitedNations: ['unitednations.yaml'] - UPS: ['ups.yaml'] + VE: ['ve.yaml'] + VN: ['vn.yaml'] ZA: ['za.yaml'] + # Meta regions + Europe: ['at.yaml', 'be_fr.yaml', 'be_nl.yaml', 'ch.yaml', 'cz.yaml', 'dk.yaml', 'de.yaml', 'es.yaml', 'fr.yaml', 'gb.yaml', 'gr.yaml', 'hr.yaml', 'hu.yaml', 'ie.yaml', 'is.yaml', 'it.yaml', 'li.yaml', 'lt.yaml', 'lv.yaml', 'nl.yaml', 'no.yaml', 'pl.yaml', 'pt.yaml', 'ro.yaml', 'sk.yaml', 'si.yaml', 'bg.yaml', 'ua.yaml'] NorthAmerica: ['ca.yaml', 'mx.yaml', 'us.yaml', 'northamericainformal.yaml'] - SouthAmerica: ['ar.yaml', 'br.yaml', 'cl.yaml', 'co.yaml', 'pe.yaml', 've.yaml'] Scandinavia: ['dk.yaml', 'is.yaml', 'no.yaml', 'se.yaml', 'fi.yaml'] - Europe: ['at.yaml', 'be_fr.yaml', 'be_nl.yaml', 'ch.yaml', 'cz.yaml', 'dk.yaml', 'de.yaml', 'es.yaml', 'fr.yaml', 'gb.yaml', 'gr.yaml', 'hr.yaml', 'hu.yaml', 'ie.yaml', 'is.yaml', 'it.yaml', 'li.yaml', 'lt.yaml', 'lv.yaml', 'nl.yaml', 'no.yaml', 'pl.yaml', 'pt.yaml', 'ro.yaml', 'sk.yaml', 'si.yaml', 'bg.yaml', 'ua.yaml'] - JP: ['jp.yaml'] - VE: ['ve.yaml'] - VN: ['vn.yaml'] - SK: ['sk.yaml'] - SI: ['si.yaml'] - SG: ['sg.yaml'] - MY: ['my.yaml'] - TH: ['th.yaml'] - NG: ['ng.yaml'] + SouthAmerica: ['ar.yaml', 'br.yaml', 'cl.yaml', 'co.yaml', 'pe.yaml', 've.yaml'] + # Organisations + ECBTARGET: ['ecbtarget.yaml'] + FederalReserve: ['federalreserve.yaml'] + FederalReserveBanks: ['federalreservebanks.yaml'] + FedEx: ['fedex.yaml'] + NERC: ['nerc.yaml'] + NYSE: ['nyse.yaml'] + TSX: ['tsx.yaml'] + UnitedNations: ['unitednations.yaml'] + UPS: ['ups.yaml'] diff --git a/lib/validation/index_validator.rb b/lib/validation/index_validator.rb index fea86a62..40ef6264 100644 --- a/lib/validation/index_validator.rb +++ b/lib/validation/index_validator.rb @@ -19,6 +19,7 @@ class Index def call(index, definition_files) defs = validate_structure!(index) + validate_order!(defs.keys) validate_file_names!(defs) validate_all_files_indexed!(defs, definition_files) validate_all_indexed_files_exist!(defs, definition_files) @@ -47,6 +48,31 @@ def validate_structure!(index) defs end + def validate_order!(order) + expected_order = order.sort_by do |name| + group = case name + when /\A[A-Z]{2}(\z|_)/ + 0 # countries + when *AGGREGATE_REGIONS + 1 # meta regions + else + 2 # organisations + end + + [group, name.downcase] + end + + unless order == expected_order + out_of_order = order.each_index.select{ |i| order[i] != expected_order[i] } + + diff = out_of_order.chunk_while{ |a, b| a + 1 == b }.flat_map do |chunk| + chunk.map{ |i| "-#{order[i]}" } + chunk.map{ |i| "+#{expected_order[i]}" } + end + + raise Errors::InvalidIndex.new("index has wrong order of definitions:\n#{diff.join("\n")}") + end + end + def validate_file_names!(defs) invalid = indexed_files(defs).reject { |file| file =~ VALID_FILE_NAME } diff --git a/spec/validation/index_validator_spec.rb b/spec/validation/index_validator_spec.rb index e0a56555..ade2e8ea 100644 --- a/spec/validation/index_validator_spec.rb +++ b/spec/validation/index_validator_spec.rb @@ -4,7 +4,7 @@ describe Definitions::Validation::Index do subject { described_class.new } - let(:index) { { 'defs' => { 'US' => ['us.yaml'], 'CA' => ['ca.yaml'] } } } + let(:index) { { 'defs' => { 'CA' => ['ca.yaml'], 'US' => ['us.yaml'] } } } let(:definition_files) { ['us.yaml', 'ca.yaml'] } context 'valid' do @@ -25,14 +25,14 @@ it 'allows aggregate regions to have no definition file of their own' do index = { 'defs' => { + 'BR' => ['br.yaml'], + 'FR' => ['fr.yaml'], + 'NO' => ['no.yaml'], + 'US' => ['us.yaml'], 'Europe' => ['fr.yaml'], 'NorthAmerica' => ['us.yaml'], 'Scandinavia' => ['no.yaml'], 'SouthAmerica' => ['br.yaml'], - 'FR' => ['fr.yaml'], - 'US' => ['us.yaml'], - 'NO' => ['no.yaml'], - 'BR' => ['br.yaml'], } } expect(subject.call(index, ['fr.yaml', 'us.yaml', 'no.yaml', 'br.yaml'])).to be true @@ -89,6 +89,66 @@ end end + context 'invalid order' do + it 'raises error if the country codes are not ordered' do + index = { 'defs' => { 'US' => ['us.yaml'], 'CA' => ['ca.yaml'] } } + expect { subject.call(index, ['us.yaml', 'us.yaml']) }.to raise_error(Definitions::Errors::InvalidIndex) { |e| + expect(e.message).to eq("index has wrong order of definitions:\n-US\n-CA\n+CA\n+US") + } + end + + it 'raises error if meta regions are not ordered' do + index = { + 'defs' => { + 'FR' => ['fr.yaml'], + 'US' => ['us.yaml'], + 'NorthAmerica' => ['us.yaml'], + 'Europe' => ['fr.yaml'], + } + } + expect { subject.call(index, ['fr.yaml', 'us.yaml']) }.to raise_error(Definitions::Errors::InvalidIndex) { |e| + expect(e.message).to eq("index has wrong order of definitions:\n-NorthAmerica\n-Europe\n+Europe\n+NorthAmerica") + } + end + + it 'raises error if organisation names are not ordered' do + index = { + 'defs' => { + 'Foo' => ['foo.yaml'], + 'Bar' => ['bar.yaml'], + } + } + expect { subject.call(index, ['foo.yaml', 'bar.yaml']) }.to raise_error(Definitions::Errors::InvalidIndex) { |e| + expect(e.message).to eq("index has wrong order of definitions:\n-Foo\n-Bar\n+Bar\n+Foo") + } + end + + it 'raises error if meta regions precede country codes' do + index = { + 'defs' => { + 'Europe' => ['fr.yaml'], + 'FR' => ['fr.yaml'], + } + } + expect { subject.call(index, ['fr.yaml']) }.to raise_error(Definitions::Errors::InvalidIndex) { |e| + expect(e.message).to eq("index has wrong order of definitions:\n-Europe\n-FR\n+FR\n+Europe") + } + end + + it 'raises error if organisation precede meta regions' do + index = { + 'defs' => { + 'FR' => ['fr.yaml'], + 'Foo' => ['foo.yaml'], + 'Europe' => ['fr.yaml'], + } + } + expect { subject.call(index, ['fr.yaml', 'foo.yaml']) }.to raise_error(Definitions::Errors::InvalidIndex) { |e| + expect(e.message).to eq("index has wrong order of definitions:\n-Foo\n-Europe\n+Europe\n+Foo") + } + end + end + context 'invalid file names' do it 'raises error if an indexed file is uppercase' do index = { 'defs' => { 'US' => ['US.yaml'] } } @@ -130,14 +190,14 @@ context 'indexed files that do not exist' do it 'raises error if the index lists a file that is not on disk' do - index = { 'defs' => { 'US' => ['us.yaml'], 'HK' => ['hk.yaml'] } } + index = { 'defs' => { 'HK' => ['hk.yaml'], 'US' => ['us.yaml'] } } expect { subject.call(index, ['us.yaml']) }.to raise_error(Definitions::Errors::InvalidIndex) { |e| expect(e.message).to eq("index.yaml lists files that do not exist: hk.yaml") } end it 'reports every missing file, sorted' do - index = { 'defs' => { 'US' => ['us.yaml', 'zz.yaml'], 'HK' => ['hk.yaml'] } } + index = { 'defs' => { 'HK' => ['hk.yaml'], 'US' => ['us.yaml', 'zz.yaml'] } } expect { subject.call(index, ['us.yaml']) }.to raise_error(Definitions::Errors::InvalidIndex) { |e| expect(e.message).to eq("index.yaml lists files that do not exist: hk.yaml, zz.yaml") } @@ -154,14 +214,14 @@ end it 'reports every mismatched region, sorted' do - index = { 'defs' => { 'US' => ['ca.yaml'], 'GB' => ['ca.yaml'] } } + index = { 'defs' => { 'GB' => ['ca.yaml'], 'US' => ['ca.yaml'] } } expect { subject.call(index, ['ca.yaml']) }.to raise_error(Definitions::Errors::InvalidIndex) { |e| expect(e.message).to include("index entries do not list their own definition file: GB (expected 'gb.yaml'), US (expected 'us.yaml')") } end it 'raises error for a region that is not a known aggregate' do - index = { 'defs' => { 'Asia' => ['jp.yaml'], 'JP' => ['jp.yaml'] } } + index = { 'defs' => { 'JP' => ['jp.yaml'], 'Asia' => ['jp.yaml'] } } expect { subject.call(index, ['jp.yaml']) }.to raise_error(Definitions::Errors::InvalidIndex) { |e| expect(e.message).to include("Asia (expected 'asia.yaml')") }