iiif-presentation 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +35 -0
  3. data/.gitignore +1 -0
  4. data/Gemfile +2 -0
  5. data/README.md +22 -2
  6. data/VERSION +1 -1
  7. data/iiif-presentation.gemspec +1 -1
  8. data/lib/iiif/hash_behaviours.rb +1 -1
  9. data/lib/iiif/presentation/canvas.rb +4 -0
  10. data/lib/iiif/presentation/service.rb +12 -0
  11. data/lib/iiif/presentation.rb +5 -4
  12. data/lib/iiif/service.rb +40 -105
  13. data/lib/iiif/v3/abstract_resource.rb +491 -0
  14. data/lib/iiif/v3/presentation/annotation.rb +74 -0
  15. data/lib/iiif/v3/presentation/annotation_collection.rb +38 -0
  16. data/lib/iiif/v3/presentation/annotation_page.rb +53 -0
  17. data/lib/iiif/v3/presentation/canvas.rb +82 -0
  18. data/lib/iiif/v3/presentation/choice.rb +51 -0
  19. data/lib/iiif/v3/presentation/collection.rb +52 -0
  20. data/lib/iiif/v3/presentation/image_resource.rb +110 -0
  21. data/lib/iiif/v3/presentation/manifest.rb +82 -0
  22. data/lib/iiif/v3/presentation/range.rb +39 -0
  23. data/lib/iiif/v3/presentation/resource.rb +30 -0
  24. data/lib/iiif/v3/presentation/sequence.rb +66 -0
  25. data/lib/iiif/v3/presentation/service.rb +51 -0
  26. data/lib/iiif/v3/presentation.rb +36 -0
  27. data/spec/fixtures/v3/manifests/complete_from_spec.json +195 -0
  28. data/spec/fixtures/v3/manifests/minimal.json +49 -0
  29. data/spec/fixtures/v3/manifests/service_only.json +14 -0
  30. data/spec/fixtures/vcr_cassettes/pul_loris_cassette.json +1 -1
  31. data/spec/fixtures/vcr_cassettes/pul_loris_cassette_v3.json +1 -0
  32. data/spec/integration/iiif/presentation/image_resource_spec.rb +0 -1
  33. data/spec/integration/iiif/service_spec.rb +17 -32
  34. data/spec/integration/iiif/v3/abstract_resource_spec.rb +202 -0
  35. data/spec/integration/iiif/v3/presentation/image_resource_spec.rb +118 -0
  36. data/spec/spec_helper.rb +6 -0
  37. data/spec/unit/iiif/presentation/canvas_spec.rb +0 -1
  38. data/spec/unit/iiif/presentation/manifest_spec.rb +1 -1
  39. data/spec/unit/iiif/v3/abstract_resource_define_methods_for_spec.rb +78 -0
  40. data/spec/unit/iiif/v3/abstract_resource_spec.rb +293 -0
  41. data/spec/unit/iiif/v3/presentation/annotation_collection_spec.rb +36 -0
  42. data/spec/unit/iiif/v3/presentation/annotation_page_spec.rb +131 -0
  43. data/spec/unit/iiif/v3/presentation/annotation_spec.rb +389 -0
  44. data/spec/unit/iiif/v3/presentation/canvas_spec.rb +337 -0
  45. data/spec/unit/iiif/v3/presentation/choice_spec.rb +120 -0
  46. data/spec/unit/iiif/v3/presentation/collection_spec.rb +55 -0
  47. data/spec/unit/iiif/v3/presentation/image_resource_spec.rb +189 -0
  48. data/spec/unit/iiif/v3/presentation/manifest_spec.rb +370 -0
  49. data/spec/unit/iiif/v3/presentation/range_spec.rb +54 -0
  50. data/spec/unit/iiif/v3/presentation/resource_spec.rb +174 -0
  51. data/spec/unit/iiif/v3/presentation/sequence_spec.rb +222 -0
  52. data/spec/unit/iiif/v3/presentation/service_spec.rb +220 -0
  53. data/spec/unit/iiif/v3/presentation/shared_examples/abstract_resource_only_keys.rb +41 -0
  54. data/spec/unit/iiif/v3/presentation/shared_examples/any_type_keys.rb +31 -0
  55. data/spec/unit/iiif/v3/presentation/shared_examples/array_only_keys.rb +40 -0
  56. data/spec/unit/iiif/v3/presentation/shared_examples/hash_only_keys.rb +40 -0
  57. data/spec/unit/iiif/v3/presentation/shared_examples/int_only_keys.rb +45 -0
  58. data/spec/unit/iiif/v3/presentation/shared_examples/numeric_only_keys.rb +45 -0
  59. data/spec/unit/iiif/v3/presentation/shared_examples/string_only_keys.rb +26 -0
  60. data/spec/unit/iiif/v3/presentation/shared_examples/uri_only_keys.rb +31 -0
  61. metadata +82 -11
  62. data/.travis.yml +0 -11
@@ -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