iiif-presentation 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +2 -2
  3. data/.gitignore +1 -0
  4. data/Gemfile +2 -0
  5. data/README.md +22 -1
  6. data/VERSION +1 -1
  7. data/iiif-presentation.gemspec +1 -1
  8. data/lib/iiif/presentation/canvas.rb +4 -0
  9. data/lib/iiif/presentation/service.rb +12 -0
  10. data/lib/iiif/presentation.rb +5 -4
  11. data/lib/iiif/service.rb +38 -103
  12. data/lib/iiif/v3/abstract_resource.rb +491 -0
  13. data/lib/iiif/v3/presentation/annotation.rb +74 -0
  14. data/lib/iiif/v3/presentation/annotation_collection.rb +38 -0
  15. data/lib/iiif/v3/presentation/annotation_page.rb +53 -0
  16. data/lib/iiif/v3/presentation/canvas.rb +82 -0
  17. data/lib/iiif/v3/presentation/choice.rb +51 -0
  18. data/lib/iiif/v3/presentation/collection.rb +52 -0
  19. data/lib/iiif/v3/presentation/image_resource.rb +110 -0
  20. data/lib/iiif/v3/presentation/manifest.rb +82 -0
  21. data/lib/iiif/v3/presentation/range.rb +39 -0
  22. data/lib/iiif/v3/presentation/resource.rb +30 -0
  23. data/lib/iiif/v3/presentation/sequence.rb +66 -0
  24. data/lib/iiif/v3/presentation/service.rb +51 -0
  25. data/lib/iiif/v3/presentation.rb +36 -0
  26. data/spec/fixtures/v3/manifests/complete_from_spec.json +195 -0
  27. data/spec/fixtures/v3/manifests/minimal.json +49 -0
  28. data/spec/fixtures/v3/manifests/service_only.json +14 -0
  29. data/spec/fixtures/vcr_cassettes/pul_loris_cassette.json +1 -1
  30. data/spec/fixtures/vcr_cassettes/pul_loris_cassette_v3.json +1 -0
  31. data/spec/integration/iiif/presentation/image_resource_spec.rb +0 -1
  32. data/spec/integration/iiif/service_spec.rb +17 -32
  33. data/spec/integration/iiif/v3/abstract_resource_spec.rb +202 -0
  34. data/spec/integration/iiif/v3/presentation/image_resource_spec.rb +118 -0
  35. data/spec/spec_helper.rb +6 -0
  36. data/spec/unit/iiif/presentation/canvas_spec.rb +0 -1
  37. data/spec/unit/iiif/presentation/manifest_spec.rb +1 -1
  38. data/spec/unit/iiif/v3/abstract_resource_define_methods_for_spec.rb +78 -0
  39. data/spec/unit/iiif/v3/abstract_resource_spec.rb +293 -0
  40. data/spec/unit/iiif/v3/presentation/annotation_collection_spec.rb +36 -0
  41. data/spec/unit/iiif/v3/presentation/annotation_page_spec.rb +131 -0
  42. data/spec/unit/iiif/v3/presentation/annotation_spec.rb +389 -0
  43. data/spec/unit/iiif/v3/presentation/canvas_spec.rb +337 -0
  44. data/spec/unit/iiif/v3/presentation/choice_spec.rb +120 -0
  45. data/spec/unit/iiif/v3/presentation/collection_spec.rb +55 -0
  46. data/spec/unit/iiif/v3/presentation/image_resource_spec.rb +189 -0
  47. data/spec/unit/iiif/v3/presentation/manifest_spec.rb +370 -0
  48. data/spec/unit/iiif/v3/presentation/range_spec.rb +54 -0
  49. data/spec/unit/iiif/v3/presentation/resource_spec.rb +174 -0
  50. data/spec/unit/iiif/v3/presentation/sequence_spec.rb +222 -0
  51. data/spec/unit/iiif/v3/presentation/service_spec.rb +220 -0
  52. data/spec/unit/iiif/v3/presentation/shared_examples/abstract_resource_only_keys.rb +41 -0
  53. data/spec/unit/iiif/v3/presentation/shared_examples/any_type_keys.rb +31 -0
  54. data/spec/unit/iiif/v3/presentation/shared_examples/array_only_keys.rb +40 -0
  55. data/spec/unit/iiif/v3/presentation/shared_examples/hash_only_keys.rb +40 -0
  56. data/spec/unit/iiif/v3/presentation/shared_examples/int_only_keys.rb +45 -0
  57. data/spec/unit/iiif/v3/presentation/shared_examples/numeric_only_keys.rb +45 -0
  58. data/spec/unit/iiif/v3/presentation/shared_examples/string_only_keys.rb +26 -0
  59. data/spec/unit/iiif/v3/presentation/shared_examples/uri_only_keys.rb +31 -0
  60. metadata +78 -7
@@ -0,0 +1,82 @@
1
+ module IIIF
2
+ module V3
3
+ module Presentation
4
+ class Canvas < IIIF::V3::AbstractResource
5
+
6
+ TYPE = 'Canvas'.freeze
7
+
8
+ def required_keys
9
+ super + %w{ id label }
10
+ end
11
+
12
+ def prohibited_keys
13
+ super + PAGING_PROPERTIES + %w{ viewing_direction format nav_date start_canvas content_annotations }
14
+ end
15
+
16
+ def int_only_keys
17
+ super + %w{ depth }
18
+ end
19
+
20
+ def array_only_keys
21
+ super + %w{ content }
22
+ end
23
+
24
+ def legal_viewing_hint_values
25
+ super + %w{ paged continuous non-paged facing-pages auto-advance }
26
+ end
27
+
28
+ def initialize(hsh={})
29
+ hsh['type'] = TYPE unless hsh.has_key? 'type'
30
+ super(hsh)
31
+ end
32
+
33
+ def validate
34
+ super
35
+
36
+ id_uri = URI.parse(self['id'])
37
+ unless self['id'] =~ /^https?:/ && id_uri.fragment.nil?
38
+ err_msg = "id must be an http(s) URI without a fragment for #{self.class}"
39
+ raise IIIF::V3::Presentation::IllegalValueError, err_msg
40
+ end
41
+
42
+ content = self['content']
43
+ if content && content.any?
44
+ unless content.all? { |entry| entry.instance_of?(IIIF::V3::Presentation::AnnotationPage) }
45
+ err_msg = 'All entries in the content list must be a IIIF::V3::Presentation::AnnotationPage'
46
+ raise IIIF::V3::Presentation::IllegalValueError, err_msg
47
+ end
48
+ content.each do |anno_page|
49
+ annos = anno_page['items']
50
+ if annos && annos.any?
51
+ unless annos.all? { |anno| anno.target == self.id }
52
+ err_msg = 'URI of the canvas must be repeated in the target field of included Annotations'
53
+ raise IIIF::V3::Presentation::IllegalValueError, err_msg
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ # "A canvas MUST have exactly one width and one height, or exactly one duration.
60
+ # It may have width, height and duration.""
61
+ height = self['height']
62
+ width = self['width']
63
+ if (!!height ^ !!width) # this is an exclusive or: forces height and width to boolean
64
+ extent_err_msg = "#{self.class} requires both height and width or neither"
65
+ raise IIIF::V3::Presentation::IllegalValueError, extent_err_msg
66
+ end
67
+ # NOTE: relaxing requirement for (exactly one width and one height, and/or exactly one duration)
68
+ # as Stanford has objects (such as txt files) for which this makes no sense
69
+ # (see sul-dlss/purl/issues/169)
70
+ # duration = self['duration']
71
+ # unless (height && width) || duration
72
+ # extent_err_msg = "#{self.class} must have (a height and a width) and/or a duration"
73
+ # raise IIIF::V3::Presentation::IllegalValueError, extent_err_msg
74
+ # end
75
+
76
+ # TODO: Content must not be associated with space or time outside of the Canvas’s dimensions,
77
+ # such as at coordinates below 0,0, greater than the height or width, before 0 seconds, or after the duration.
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,51 @@
1
+ module IIIF
2
+ module V3
3
+ module Presentation
4
+ class Choice < IIIF::V3::AbstractResource
5
+
6
+ TYPE = 'Choice'.freeze
7
+
8
+ def prohibited_keys
9
+ super + CONTENT_RESOURCE_PROPERTIES + PAGING_PROPERTIES +
10
+ %w{ nav_date viewing_direction start_canvas content_annotations }
11
+ end
12
+
13
+ def any_type_keys
14
+ super + %w{ default }
15
+ end
16
+
17
+ def string_only_keys
18
+ super + %w{ choice_hint }
19
+ end
20
+
21
+ def array_only_keys;
22
+ super + %w{ items };
23
+ end
24
+
25
+ def legal_viewing_hint_values
26
+ %w{ none }
27
+ end
28
+
29
+ def legal_choice_hint_values
30
+ %w{ client user }
31
+ end
32
+
33
+ def initialize(hsh={})
34
+ hsh['type'] = TYPE unless hsh.has_key? 'type'
35
+ super(hsh)
36
+ end
37
+
38
+ def validate
39
+ super
40
+
41
+ if self.has_key?('choice_hint')
42
+ unless self.legal_choice_hint_values.include?(self['choice_hint'])
43
+ m = "choiceHint for #{self.class} must be one of #{self.legal_choice_hint_values}."
44
+ raise IIIF::V3::Presentation::IllegalValueError, m
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,52 @@
1
+ module IIIF
2
+ module V3
3
+ module Presentation
4
+ class Collection < IIIF::V3::AbstractResource
5
+
6
+ TYPE = 'Collection'.freeze
7
+
8
+ def required_keys
9
+ super + %w{ id label }
10
+ end
11
+
12
+ def array_only_keys
13
+ super + %w{ collections manifests }
14
+ end
15
+
16
+ # TODO: navDate (collection or manifest only) - The value must be an xsd:dateTime literal in UTC, expressed in the form “YYYY-MM-DDThh:mm:ssZ”; There must be at most one navDate associated with any given resource.
17
+
18
+ # TODO: paging properties
19
+ # Collection, AnnotationCollection, (formerly layer --> AnnotationPage???) allow; forbidden o.w.
20
+ # ---
21
+ # first, last, next, prev
22
+ # id is URI, but may have other info
23
+ # total, startIndex
24
+ # The value must be a non-negative integer.
25
+
26
+ def legal_viewing_hint_values
27
+ %w{ auto-advance together }
28
+ end
29
+
30
+ def initialize(hsh={})
31
+ hsh['type'] = TYPE unless hsh.has_key? 'type'
32
+ super(hsh)
33
+ end
34
+
35
+ def validate
36
+ super
37
+ # TODO: each member of collections and manifests must be a Hash
38
+ # TODO: each member of collections and manifests MUST have id, type, and label
39
+ # TODO: navDate (collection or manifest only) - The value must be an xsd:dateTime literal in UTC, expressed in the form “YYYY-MM-DDThh:mm:ssZ”; There must be at most one navDate associated with any given resource.
40
+
41
+ # TODO: paging properties
42
+ # Collection, AnnotationCollection, (formerly layer --> AnnotationPage???) allow; forbidden o.w.
43
+ # ---
44
+ # first, last, next, prev
45
+ # id is URI, but may have other info
46
+ # total, startIndex
47
+ # The value must be a non-negative integer.
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,110 @@
1
+ require 'faraday'
2
+
3
+ module IIIF
4
+ module V3
5
+ module Presentation
6
+ class ImageResource < Resource
7
+
8
+ TYPE = 'Image'.freeze
9
+
10
+ def initialize(hsh={})
11
+ hsh['type'] = TYPE unless hsh.has_key? 'type'
12
+ super(hsh)
13
+ end
14
+
15
+ class << self
16
+ IMAGE_API_DEFAULT_PARAMS = '/full/!200,200/0/default.jpg'.freeze
17
+ DEFAULT_FORMAT = 'image/jpeg'.freeze
18
+ # Create a new ImageResource that includes a IIIF Image API Service
19
+ # See http://iiif.io/api/presentation/2.0/#image-resources
20
+ #
21
+ # Params
22
+ # * :service_id (required) - The base URI for the image on the image
23
+ # server.
24
+ # * :resource_id - The id for the resource; if supplied this should
25
+ # resolve to an actual image. Default:
26
+ # "#{:service_id}/full/!200,200/0/default.jpg"
27
+ # * :format - The format of the image that is returned when
28
+ # `:resource_id` is resolved. Default: 'image/jpeg'
29
+ # * :height (Integer)
30
+ # * :profile (String)
31
+ # * :width (Integer) - If width, height, and profile are not supplied,
32
+ # this method will try to get the info from the server (based on
33
+ # :resource_id) and raise an Exception if this is not possible for
34
+ # some reason.
35
+ # * :copy_info (bool)- Even if width and height are supplied, try to
36
+ # get the info.json from the server and copy it in. Default: false
37
+ #
38
+ # Raises:
39
+ # * KeyError if `:service_id` is not supplied
40
+ # * Expections related to HTTP problems if a call to an image server fails
41
+ #
42
+ # The result is something like this:
43
+ #
44
+ # {
45
+ # "id":"http://www.example.org/iiif/book1/res/page1.jpg",
46
+ # "type": "Image",
47
+ # "format": "image/jpeg",
48
+ # "service": {
49
+ # "@context": "http://iiif.io/api/image/2/context.json",
50
+ # "id":"http://www.example.org/images/book1-page1",
51
+ # "profile":"http://iiif.io/api/image/2/profiles/level2.json",
52
+ # },
53
+ # "height":2000,
54
+ # "width":1500
55
+ # }
56
+ #
57
+ def create_image_api_image_resource(params={})
58
+
59
+ service_id = params.fetch(:service_id)
60
+ resource_id_default = "#{service_id}#{IMAGE_API_DEFAULT_PARAMS}"
61
+ resource_id = params.fetch(:resource_id, resource_id_default)
62
+ format = params.fetch(:format, DEFAULT_FORMAT)
63
+ height = params.fetch(:height, nil)
64
+ profile = params.fetch(:profile, nil)
65
+ width = params.fetch(:width, nil)
66
+ copy_info = params.fetch(:copy_info, false)
67
+
68
+ have_whp = [width, height, profile].all? { |prop| !prop.nil? }
69
+
70
+ remote_info = get_info(service_id) if !have_whp || copy_info
71
+
72
+ resource = self.new
73
+ resource['id'] = resource_id
74
+ resource.format = format
75
+ resource.width = width.nil? ? remote_info['width'] : width
76
+ resource.height = height.nil? ? remote_info['height'] : height
77
+ resource_service = Service.new
78
+ if copy_info
79
+ resource_service.merge!(remote_info)
80
+ resource_service['id'] ||= resource_service.delete('@id')
81
+ else
82
+ resource_service['id'] = service_id
83
+ if profile.nil?
84
+ if remote_info['profile'].kind_of?(Array)
85
+ resource_service['profile'] = remote_info['profile'][0]
86
+ else
87
+ resource_service['profile'] = remote_info['profile']
88
+ end
89
+ else
90
+ resource_service['profile'] = profile
91
+ end
92
+ end
93
+ resource.service = [resource_service]
94
+ return resource
95
+ end
96
+
97
+ protected
98
+ def get_info(svc_id)
99
+ conn = Faraday.new("#{svc_id}/info.json") do |c|
100
+ c.use Faraday::Response::RaiseError
101
+ c.adapter :net_http
102
+ end
103
+ resp = conn.get # raises exceptions that indicate HTTP problems
104
+ JSON.parse(resp.body)
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,82 @@
1
+ module IIIF
2
+ module V3
3
+ module Presentation
4
+ class Manifest < IIIF::V3::AbstractResource
5
+
6
+ TYPE = 'Manifest'.freeze
7
+
8
+ def required_keys
9
+ # NOTE: relaxing requirement for items as Universal Viewer currently only accepts sequences
10
+ # see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
11
+ # super + %w{ id label items }
12
+ super + %w{ id label }
13
+ end
14
+
15
+ def prohibited_keys
16
+ super + CONTENT_RESOURCE_PROPERTIES + PAGING_PROPERTIES + %w{ start_canvas content_annotation }
17
+ end
18
+
19
+ def uri_only_keys
20
+ super + %w{ id }
21
+ end
22
+
23
+ def array_only_keys
24
+ # NOTE: allowing 'items' or 'sequences' as Universal Viewer currently only accepts sequences
25
+ # see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
26
+ # super + %w{ items structures }
27
+ super + %w{ items structures sequences }
28
+ end
29
+
30
+ def legal_viewing_hint_values
31
+ %w{ individuals paged continuous auto-advance }
32
+ end
33
+
34
+ def initialize(hsh={})
35
+ hsh['type'] = TYPE unless hsh.has_key? 'type'
36
+ super(hsh)
37
+ end
38
+
39
+ def validate
40
+ super # also checks navDate format
41
+
42
+ unless self['id'] =~ /^https?:/
43
+ err_msg = "id must be an http(s) URI for #{self.class}"
44
+ raise IIIF::V3::Presentation::IllegalValueError, err_msg
45
+ end
46
+
47
+ # Items object list
48
+ unless self&.[]('items')&.any?
49
+ m = 'The items list must have at least one entry (and it must be a IIIF::V3::Presentation::Canvas)'
50
+ raise IIIF::V3::Presentation::MissingRequiredKeyError, m
51
+ end
52
+ validate_items_list(self['items']) if self['items']
53
+
54
+ # TODO: when embedding a sequence without any extensions within a manifest, the sequence must not have the @context field.
55
+
56
+ # TODO: AnnotationLists must not be embedded within the manifest
57
+
58
+ if self['structures']
59
+ unless self['structures'].all? { |entry| entry.instance_of?(IIIF::V3::Presentation::Range)}
60
+ m = 'All entries in the structures list must be a IIIF::V3::Presentation::Range'
61
+ raise IIIF::V3::Presentation::IllegalValueError, m
62
+ end
63
+ end
64
+ end
65
+
66
+ # NOTE: allowing 'items' or 'sequences' as Universal Viewer currently only accepts sequences
67
+ # see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
68
+ def validate_items_list(items_array)
69
+ unless items_array.size >= 1
70
+ m = 'The items list must have at least one entry (and it must be a IIIF::V3::Presentation::Canvas)'
71
+ raise IIIF::V3::Presentation::MissingRequiredKeyError, m
72
+ end
73
+
74
+ unless items_array.all? { |entry| entry.instance_of?(IIIF::V3::Presentation::Canvas) }
75
+ m = 'All entries in the items list must be a IIIF::V3::Presentation::Canvas'
76
+ raise IIIF::V3::Presentation::IllegalValueError, m
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,39 @@
1
+ module IIIF
2
+ module V3
3
+ module Presentation
4
+ # Ranges are linked or embedded within the manifest in a structures field
5
+ class Range < Sequence
6
+
7
+ TYPE = 'Range'.freeze
8
+
9
+ def required_keys
10
+ super + %w{ id label }
11
+ end
12
+
13
+ # TODO: contentAnnotations: links to AnnotationCollection
14
+ # TODO: startCanvas: A link from a Sequence or Range to a Canvas that is contained within it
15
+
16
+ def array_only_keys
17
+ super + %w{ members }
18
+ end
19
+
20
+ def legal_viewing_hint_values
21
+ super + %w{ top }
22
+ end
23
+
24
+ def initialize(hsh={})
25
+ hsh['type'] = TYPE unless hsh.has_key? 'type'
26
+ super(hsh)
27
+ end
28
+
29
+ def validate
30
+ super
31
+ # TODO: Ranges must have URIs and they should be http(s) URIs.
32
+ # TODO: Values of the members array must be canvas or range
33
+ # TODO: contentAnnotations: links to AnnotationCollection
34
+ # TODO: startCanvas: A link from a Sequence or Range to a Canvas that is contained within it
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ module IIIF
2
+ module V3
3
+ module Presentation
4
+ # class for generic content resource
5
+ class Resource < IIIF::V3::AbstractResource
6
+
7
+ def required_keys
8
+ super + %w{ id }
9
+ end
10
+
11
+ def prohibited_keys
12
+ super + PAGING_PROPERTIES + %w{ nav_date viewing_direction start_canvas content_annotations}
13
+ end
14
+
15
+ def uri_only_keys
16
+ super + %w{ id }
17
+ end
18
+
19
+ def validate
20
+ super
21
+
22
+ unless self['id'] =~ /^https?:/
23
+ err_msg = "id must be an http(s) URI for #{self.class}"
24
+ raise IIIF::V3::Presentation::IllegalValueError, err_msg
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,66 @@
1
+ module IIIF
2
+ module V3
3
+ module Presentation
4
+ class Sequence < IIIF::V3::AbstractResource
5
+
6
+ TYPE = 'Sequence'.freeze
7
+
8
+ # NOTE: relaxing requirement for items as Universal Viewer currently only accepts canvases
9
+ # see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
10
+ # def required_keys
11
+ # super + %w{ items }
12
+ # end
13
+
14
+ def prohibited_keys
15
+ super + CONTENT_RESOURCE_PROPERTIES + PAGING_PROPERTIES + %w{ nav_date content_annotations }
16
+ end
17
+
18
+ # NOTE: allowing 'items' or 'canvases' as Universal Viewer currently only accepts canvases
19
+ # see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
20
+ def array_only_keys
21
+ super + %w{ canvases }
22
+ end
23
+
24
+ def legal_viewing_hint_values
25
+ %w{ individuals paged continuous auto-advance }
26
+ end
27
+
28
+ def initialize(hsh={})
29
+ hsh['type'] = TYPE unless hsh.has_key? 'type'
30
+ super(hsh)
31
+ end
32
+
33
+ def validate
34
+ super
35
+
36
+ # Canvas object list
37
+ # NOTE: allowing 'items' or 'canvases' as Universal Viewer currently only accepts canvases
38
+ # see https://github.com/sul-dlss/osullivan/issues/27, sul-dlss/purl/issues/167
39
+ unless (self['items'] && self['items'].any?) ||
40
+ (self['canvases'] && self['canvases'].any?)
41
+ m = 'The (items or canvases) list must have at least one entry (and it must be a IIIF::V3::Presentation::Canvas)'
42
+ raise IIIF::V3::Presentation::MissingRequiredKeyError, m
43
+ end
44
+ validate_canvas_list(self['items']) if self['items']
45
+ validate_canvas_list(self['canvases']) if self['canvases']
46
+
47
+ # TODO: startCanvas: A link from a Sequence or Range to a Canvas that is contained within it
48
+
49
+ # TODO: All external Sequences must have a dereference-able http(s) URI
50
+ end
51
+
52
+ def validate_canvas_list(canvas_array)
53
+ unless canvas_array.size >= 1
54
+ m = 'The (items or canvases) list must have at least one entry (and it must be a IIIF::V3::Presentation::Canvas)'
55
+ raise IIIF::V3::Presentation::MissingRequiredKeyError, m
56
+ end
57
+
58
+ unless canvas_array.all? { |entry| entry.instance_of?(IIIF::V3::Presentation::Canvas) }
59
+ m = 'All entries in the (items or canvases) list must be a IIIF::V3::Presentation::Canvas'
60
+ raise IIIF::V3::Presentation::IllegalValueError, m
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,51 @@
1
+ module IIIF
2
+ module V3
3
+ module Presentation
4
+ # See http://prezi3.iiif.io/api/annex/services for more info
5
+ class Service < AbstractResource
6
+
7
+ # constants included here for convenience
8
+ IIIF_IMAGE_V2_TYPE = 'ImageService2'.freeze
9
+ IIIF_IMAGE_V2_LEVEL1_PROFILE = 'http://iiif.io/api/image/2/level1.json'.freeze
10
+ IIIF_AUTHENTICATION_V1_LOGIN_PROFILE = 'http://iiif.io/api/auth/1/login'.freeze
11
+ IIIF_AUTHENTICATION_V1_TOKEN_PROFILE = 'http://iiif.io/api/auth/1/token'.freeze
12
+
13
+ # service class doesn't require type
14
+ def required_keys
15
+ super.reject {|el| el == 'type' }
16
+ end
17
+
18
+ def prohibited_keys
19
+ super + CONTENT_RESOURCE_PROPERTIES + PAGING_PROPERTIES +
20
+ %w{ nav_date viewing_direction start_canvas content_annotations }
21
+ end
22
+
23
+ def uri_only_keys
24
+ super + %w{ @context id @id }
25
+ end
26
+
27
+ def any_type_keys
28
+ super + %w{ profile }
29
+ end
30
+
31
+ def validate
32
+ super
33
+ if IIIF_IMAGE_V2_TYPE == self['type'] || IIIF_IMAGE_V2_TYPE == self['@type']
34
+ unless self.has_key?('id') || self.has_key?('@id')
35
+ m = "id or @id values are required for IIIF::V3::Presentation::Service with type or @type #{IIIF_IMAGE_V2_TYPE}"
36
+ raise IIIF::V3::Presentation::MissingRequiredKeyError, m
37
+ end
38
+ if self.has_key?('id') && self.has_key?('@id') && (self['@id'] != self['id'])
39
+ m = "id and @id values must match for IIIF::V3::Presentation::Service with type or @type #{IIIF_IMAGE_V2_TYPE}"
40
+ raise IIIF::V3::Presentation::IllegalValueError, m
41
+ end
42
+ unless self.has_key?('profile')
43
+ m = "profile should be present for IIIF::V3::Presentation::Service with type or @type #{IIIF_IMAGE_V2_TYPE}"
44
+ raise IIIF::V3::Presentation::MissingRequiredKeyError, m
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,36 @@
1
+ require_relative 'abstract_resource'
2
+ require_relative '../ordered_hash'
3
+
4
+ # NOTE: image_resource must follow resource due to inheritance
5
+ # NOTE: range must follow sequence due to inheritance
6
+ %w{
7
+ annotation
8
+ annotation_collection
9
+ annotation_page
10
+ canvas
11
+ choice
12
+ collection
13
+ manifest
14
+ resource
15
+ image_resource
16
+ sequence
17
+ range
18
+ service
19
+ }.each do |f|
20
+ require File.join(File.dirname(__FILE__), 'presentation', f)
21
+ end
22
+
23
+ module IIIF
24
+ module V3
25
+ module Presentation
26
+ CONTEXT ||= [
27
+ 'http://www.w3.org/ns/anno.jsonld',
28
+ 'http://iiif.io/api/presentation/3/context.json'
29
+ ]
30
+
31
+ class MissingRequiredKeyError < StandardError; end
32
+ class ProhibitedKeyError < StandardError; end
33
+ class IllegalValueError < StandardError; end
34
+ end
35
+ end
36
+ end