Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V3 annotation, annotationPage and choice object improvements #30

Merged
merged 4 commits into from
Jul 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/iiif/v3/abstract_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ def define_accessor_methods(*keys, &validation)

private
def validate_uri(val, key)
unless val.kind_of?(String) && val =~ URI::regexp
unless val.kind_of?(String) && val =~ /\A#{URI::regexp}\z/
m = "#{key} value must be a String containing a URI for #{self.class}"
raise IIIF::V3::Presentation::IllegalValueError, m
end
Expand Down
52 changes: 45 additions & 7 deletions lib/iiif/v3/presentation/annotation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,35 @@ module V3
module Presentation
class Annotation < IIIF::V3::AbstractResource

TYPE = 'Annotation'
TYPE = 'Annotation'.freeze

def required_keys
super + %w{ motivation }
super + %w{ id motivation target }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not seeing target anywhere in the Presentation 3.0 API JSON-examples. Where does this come from?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Annotation itself isn't in the 3.0 API; it has its own spec: http://www.openannotation.org/spec/core/ In the spec it is hasTarget but our JSON expresses it as target

I learned about Annotations for the Triannon linked data project I did a while ago; but mostly I looked at examples -- our purl, Tom Crane's, etc.

end

def abstract_resource_only_keys
super + [ { key: 'body', type: IIIF::V3::Presentation::Resource } ]
def prohibited_keys
super + CONTENT_RESOURCE_PROPERTIES + PAGING_PROPERTIES +
%w{ nav_date viewing_direction start_canvas content_annotations }
end

def any_type_keys
super + %w{ body }
end

def uri_only_keys
super + %w{ id }
end

def string_only_keys
super + %w{ time_mode }
super + %w{ motivation time_mode }
end

def legal_time_mode_values
%w{ trim scale loop }
%w{ trim scale loop }.freeze
end

def legal_viewing_hint_values
super + %w{ none }
end

def initialize(hsh={})
Expand All @@ -30,7 +43,32 @@ def initialize(hsh={})
def validate
super

# time mode values
if self.has_key?('body') && self['body'].kind_of?(IIIF::V3::Presentation::ImageResource)
img_res_class_str = "IIIF::V3::Presentation::ImageResource"

unless self.motivation == 'painting'
m = "#{self.class} motivation must be 'painting' when body is a kind of #{img_res_class_str}"
raise IIIF::V3::Presentation::IllegalValueError, m
end

body_resource = self['body']
body_id = body_resource['id']
if body_id && body_id =~ /^https?:/
validate_uri(body_id, 'anno body ImageResource id') # can raise IllegalValueError
else
m = "when #{self.class} body is a kind of #{img_res_class_str}, ImageResource id must be an http(s) URI"
raise IIIF::V3::Presentation::IllegalValueError, m
end

body_service = *body_resource['service']
body_service_context = *body_resource['service']['@context']
expected_context = IIIF::V3::Presentation::Service::IIIF_IMAGE_V2_CONTEXT
unless body_service && body_service_context && body_service_context.include?(expected_context)
m = "when #{self.class} body is a kind of #{img_res_class_str}, ImageResource's service @context must include #{expected_context}"
raise IIIF::V3::Presentation::IllegalValueError, m
end
end

if self.has_key?('time_mode')
unless self.legal_time_mode_values.include?(self['time_mode'])
m = "timeMode for #{self.class} must be one of #{self.legal_time_mode_values}."
Expand Down
32 changes: 28 additions & 4 deletions lib/iiif/v3/presentation/annotation_page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,27 @@ module V3
module Presentation
class AnnotationPage < IIIF::V3::AbstractResource

TYPE = 'AnnotationPage'
TYPE = 'AnnotationPage'.freeze

def required_keys
super + %w{ id }
end

def prohibited_keys
super + CONTENT_RESOURCE_PROPERTIES +
%w{ first last total nav_date viewing_direction start_canvas content_annotations }
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking through the 3.0 (alpha) spec, I couldn't find e.g. content_annotations. I guess I must be missing something, otherwise you wouldn't have added this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

contentAnnotations is how it would appear in the spec.


def uri_only_keys
super + %w{ id }
end

def array_only_keys;
super + %w{ items };
super + %w{ items }
end

def legal_viewing_hint_values
super + %w{ none }
end

def initialize(hsh={})
Expand All @@ -20,9 +33,20 @@ def initialize(hsh={})

def validate
super
# TODO: Each member or resources must be a kind of Annotation
end

unless self['id'] =~ /^https?:/
err_msg = "id must be an http(s) URI for #{self.class}"
raise IIIF::V3::Presentation::IllegalValueError, err_msg
end

items = self['items']
if items && items.any?
unless items.all? { |entry| entry.instance_of?(IIIF::V3::Presentation::Annotation) }
err_msg = 'All entries in the items list must be a IIIF::V3::Presentation::Annotation'
raise IIIF::V3::Presentation::IllegalValueError, err_msg
end
end
end
end
end
end
Expand Down
12 changes: 10 additions & 2 deletions lib/iiif/v3/presentation/choice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@ module V3
module Presentation
class Choice < IIIF::V3::AbstractResource

TYPE = 'Choice'
TYPE = 'Choice'.freeze

def prohibited_keys
super + CONTENT_RESOURCE_PROPERTIES + PAGING_PROPERTIES +
%w{ nav_date viewing_direction start_canvas content_annotations }
end

def any_type_keys
super + %w{ default }
end

def string_only_keys
super + %w{ choice_hint }
Expand All @@ -29,7 +38,6 @@ def initialize(hsh={})
def validate
super

# time mode values
if self.has_key?('choice_hint')
unless self.legal_choice_hint_values.include?(self['choice_hint'])
m = "choiceHint for #{self.class} must be one of #{self.legal_choice_hint_values}."
Expand Down
128 changes: 126 additions & 2 deletions spec/unit/iiif/v3/presentation/annotation_page_spec.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,131 @@
describe IIIF::V3::Presentation::AnnotationPage do

describe "#{described_class}.define_methods_for_array_only_keys" do
it_behaves_like 'it has the appropriate methods for array-only keys v3'
describe '#required_keys' do
it 'id' do
expect(subject.required_keys).to include('id')
end
end

describe '#prohibited_keys' do
it 'contains the expected key names' do
keys = described_class::CONTENT_RESOURCE_PROPERTIES +
%w{
first
last
total
nav_date
viewing_direction
start_canvas
content_annotations
}
expect(subject.prohibited_keys).to include(*keys)
end
end

describe '#uri_only_keys' do
it 'id' do
expect(subject.uri_only_keys).to include('id')
end
end

describe '#array_only_keys' do
it 'items' do
expect(subject.array_only_keys).to include('items')
end
end

describe '#legal_viewing_hint_values' do
it 'contains none' do
expect(subject.legal_viewing_hint_values).to contain_exactly('none')
end
end

describe '#initialize' do
it 'sets type to AnnotationPage by default' do
expect(subject['type']).to eq 'AnnotationPage'
end
it 'allows subclasses to override type' do
subclass = Class.new(described_class) do
def initialize(hsh={})
hsh = { 'type' => 'a:SubClass' }
super(hsh)
end
end
sub = subclass.new
expect(sub['type']).to eq 'a:SubClass'
end
it 'allows type to be passed in' do
ap = described_class.new('type' => 'bar')
expect(ap.type).to eq 'bar'
end
end

describe '#validate' do
it 'raises IllegalValueError if id is not URI' do
exp_err_msg = "id value must be a String containing a URI for #{described_class}"
subject['id'] = 'foo'
expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
end
it 'raises IllegalValueError if id is not http(s)' do
subject['id'] = 'ftp://www.example.org'
exp_err_msg = "id must be an http(s) URI for #{described_class}"
expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
end
it 'raises IllegalValueError for items entry that is not an Annotation' do
subject['id'] = 'http://example.com/iiif3/annotation_page/666'
subject['items'] = [IIIF::V3::Presentation::ImageResource.new, IIIF::V3::Presentation::Annotation.new]
exp_err_msg = "All entries in the items list must be a IIIF::V3::Presentation::Annotation"
expect { subject.validate }.to raise_error(IIIF::V3::Presentation::IllegalValueError, exp_err_msg)
end
end

describe 'realistic examples' do
let(:ap_id) { 'http://example.com/iiif3/annotation_page/666' }
let(:anno) { IIIF::V3::Presentation::Annotation.new(
'id' => 'http://example.com/anno/666',
'target' => 'http://example.com/canvas/abc'
)}

describe 'stanford (purl code)' do
let(:anno_page) {
anno_page = described_class.new
anno_page['id'] = ap_id
anno_page.items << anno
anno_page
}
it 'validates' do
expect{anno_page.validate}.not_to raise_error
end
it 'has expected required values' do
expect(anno_page.id).to eq ap_id
expect(anno_page['type']).to eq 'AnnotationPage'
end
it 'has expected additional values' do
expect(anno_page.items).to eq [anno]
end
end

describe 'two items' do
let(:anno2) { IIIF::V3::Presentation::Annotation.new(
'id' => 'http://example.com/anno/333',
'target' => 'http://example.com/canvas/abc'
)}
let(:anno_page) {
anno_page = described_class.new
anno_page['id'] = ap_id
anno_page.items = [anno, anno2]
anno_page
}
it 'validates' do
expect{anno_page.validate}.not_to raise_error
end
it 'has expected required values' do
expect(anno_page.id).to eq ap_id
expect(anno_page['type']).to eq 'AnnotationPage'
end
it 'has expected additional values' do
expect(anno_page.items).to eq [anno, anno2]
end
end
end
end
Loading