cocina-models 0.29.0 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -3
  3. data/README.md +11 -0
  4. data/cocina-models.gemspec +5 -0
  5. data/exe/generator +9 -0
  6. data/lib/cocina/generator.rb +18 -0
  7. data/lib/cocina/generator/generator.rb +80 -0
  8. data/lib/cocina/generator/schema.rb +84 -0
  9. data/lib/cocina/generator/schema_array.rb +20 -0
  10. data/lib/cocina/generator/schema_base.rb +49 -0
  11. data/lib/cocina/generator/schema_ref.rb +16 -0
  12. data/lib/cocina/generator/schema_value.rb +60 -0
  13. data/lib/cocina/generator/vocab.rb +63 -0
  14. data/lib/cocina/models.rb +51 -25
  15. data/lib/cocina/models/access.rb +10 -0
  16. data/lib/cocina/models/admin_policy.rb +13 -56
  17. data/lib/cocina/models/admin_policy_administrative.rb +11 -0
  18. data/lib/cocina/models/administrative.rb +14 -0
  19. data/lib/cocina/models/catalog_link.rb +4 -1
  20. data/lib/cocina/models/collection.rb +21 -31
  21. data/lib/cocina/models/collection_identification.rb +9 -0
  22. data/lib/cocina/models/description.rb +1 -8
  23. data/lib/cocina/models/dro.rb +34 -70
  24. data/lib/cocina/models/dro_access.rb +16 -0
  25. data/lib/cocina/models/dro_structural.rb +14 -0
  26. data/lib/cocina/models/embargo.rb +16 -0
  27. data/lib/cocina/models/file.rb +20 -36
  28. data/lib/cocina/models/file_administrative.rb +10 -0
  29. data/lib/cocina/models/file_set.rb +8 -15
  30. data/lib/cocina/models/file_set_structural.rb +9 -0
  31. data/lib/cocina/models/geographic.rb +10 -0
  32. data/lib/cocina/models/identification.rb +11 -0
  33. data/lib/cocina/models/message_digest.rb +17 -0
  34. data/lib/cocina/models/presentation.rb +12 -0
  35. data/lib/cocina/models/release_tag.rb +12 -7
  36. data/lib/cocina/models/request_admin_policy.rb +15 -3
  37. data/lib/cocina/models/request_collection.rb +21 -4
  38. data/lib/cocina/models/request_dro.rb +32 -11
  39. data/lib/cocina/models/request_dro_structural.rb +13 -0
  40. data/lib/cocina/models/request_file.rb +15 -6
  41. data/lib/cocina/models/request_file_set.rb +7 -9
  42. data/lib/cocina/models/request_file_set_structural.rb +9 -0
  43. data/lib/cocina/models/sequence.rb +2 -5
  44. data/lib/cocina/models/title.rb +12 -0
  45. data/lib/cocina/models/validator.rb +22 -0
  46. data/lib/cocina/models/version.rb +1 -1
  47. data/lib/cocina/models/vocab.rb +45 -60
  48. data/openapi.yml +695 -0
  49. metadata +101 -9
  50. data/lib/cocina/models/admin_policy_attributes.rb +0 -21
  51. data/lib/cocina/models/collection_attributes.rb +0 -22
  52. data/lib/cocina/models/dro_attributes.rb +0 -22
  53. data/lib/cocina/models/file_attributes.rb +0 -25
  54. data/lib/cocina/models/file_set_attributes.rb +0 -16
  55. data/lib/cocina/models/types.rb +0 -10
@@ -5,23 +5,34 @@ require 'zeitwerk'
5
5
  require 'dry-struct'
6
6
  require 'dry-types'
7
7
  require 'json'
8
+ require 'openapi_parser'
9
+ require 'active_support/core_ext/hash/indifferent_access'
10
+ require 'yaml'
8
11
 
9
12
  # Help Zeitwerk find some of our classes
10
13
  class CocinaModelsInflector < Zeitwerk::Inflector
14
+ # rubocop:disable Metrics/CyclomaticComplexity
15
+ # rubocop:disable Metrics/MethodLength
11
16
  def camelize(basename, _abspath)
12
17
  case basename
13
18
  when 'dro'
14
19
  'DRO'
15
- when 'dro_builder'
16
- 'DROBuilder'
17
20
  when 'request_dro'
18
21
  'RequestDRO'
22
+ when 'dro_access'
23
+ 'DROAccess'
24
+ when 'dro_structural'
25
+ 'DROStructural'
26
+ when 'request_dro_structural'
27
+ 'RequestDROStructural'
19
28
  when 'version'
20
29
  'VERSION'
21
30
  else
22
31
  super
23
32
  end
24
33
  end
34
+ # rubocop:enable Metrics/CyclomaticComplexity
35
+ # rubocop:enable Metrics/MethodLength
25
36
  end
26
37
 
27
38
  loader = Zeitwerk::Loader.new
@@ -36,43 +47,58 @@ module Cocina
36
47
  # Raised when the type attribute is not valid.
37
48
  class UnknownTypeError < Error; end
38
49
 
50
+ # Raised when an error occurs validating against openapi.
51
+ class ValidationError < Error; end
52
+
39
53
  # Base class for Cocina Structs
40
54
  class Struct < Dry::Struct
41
55
  transform_keys(&:to_sym)
56
+ schema schema.strict
57
+ end
58
+
59
+ # DRY Types
60
+ module Types
61
+ include Dry.Types()
42
62
  end
43
63
 
44
64
  # @param [Hash] dyn a ruby hash representation of the JSON serialization of a collection or DRO
45
- # @return [DRO,Collection]
65
+ # @param [boolean] validate
66
+ # @return [DRO,Collection,AdminPolicy]
46
67
  # @raises [UnknownTypeError] if a valid type is not found in the data
47
68
  # @raises [KeyError] if a type field cannot be found in the data
48
- def self.build(dyn)
49
- case dyn.fetch('type')
50
- when *DRO::TYPES
51
- DRO.new(dyn)
52
- when *Collection::TYPES
53
- Collection.new(dyn)
54
- when *AdminPolicy::TYPES
55
- AdminPolicy.new(dyn)
56
- else
57
- raise UnknownTypeError, "Unknown type: '#{dyn.fetch('type')}'"
58
- end
69
+ # @raises [ValidationError] if hash representation fails openapi validation
70
+ def self.build(dyn, validate: true)
71
+ clazz = case dyn.fetch('type')
72
+ when *DRO::TYPES
73
+ DRO
74
+ when *Collection::TYPES
75
+ Collection
76
+ when *AdminPolicy::TYPES
77
+ AdminPolicy
78
+ else
79
+ raise UnknownTypeError, "Unknown type: '#{dyn.fetch('type')}'"
80
+ end
81
+ clazz.new(dyn, validate: validate)
59
82
  end
60
83
 
61
84
  # @param [Hash] dyn a ruby hash representation of the JSON serialization of a request for a Collection or DRO
85
+ # @param [boolean] validate
62
86
  # @return [RequestDRO,RequestCollection,RequestAdminPolicy]
63
87
  # @raises [UnknownTypeError] if a valid type is not found in the data
64
88
  # @raises [KeyError] if a type field cannot be found in the data
65
- def self.build_request(dyn)
66
- case dyn.fetch('type')
67
- when *DRO::TYPES
68
- RequestDRO.new(dyn)
69
- when *Collection::TYPES
70
- RequestCollection.new(dyn)
71
- when *AdminPolicy::TYPES
72
- RequestAdminPolicy.new(dyn)
73
- else
74
- raise UnknownTypeError, "Unknown type: '#{dyn.fetch('type')}'"
75
- end
89
+ # @raises [ValidationError] if hash representation fails openapi validation
90
+ def self.build_request(dyn, validate: true)
91
+ clazz = case dyn.fetch('type')
92
+ when *DRO::TYPES
93
+ RequestDRO
94
+ when *Collection::TYPES
95
+ RequestCollection
96
+ when *AdminPolicy::TYPES
97
+ RequestAdminPolicy
98
+ else
99
+ raise UnknownTypeError, "Unknown type: '#{dyn.fetch('type')}'"
100
+ end
101
+ clazz.new(dyn, validate: validate)
76
102
  end
77
103
  end
78
104
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ class Access < Struct
6
+ # Access level
7
+ attribute :access, Types::Strict::String.default('dark').enum('world', 'stanford', 'location-based', 'citation-only', 'dark').meta(omittable: true)
8
+ end
9
+ end
10
+ end
@@ -2,67 +2,24 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- # An admin policy object.
6
5
  class AdminPolicy < Struct
7
6
  include Checkable
8
7
 
9
- TYPES = [
10
- Vocab.admin_policy
11
- ].freeze
8
+ TYPES = ['http://cocina.sul.stanford.edu/models/admin_policy.jsonld'].freeze
12
9
 
13
- # Subschema for access concerns
14
- class Access < Struct
15
- end
16
-
17
- # Subschema for administrative concerns
18
- class Administrative < Struct
19
- # This was copied from the ActiveFedora defaults: Dor::AdminPolicyObject.new.defaultObjectRights.content
20
- DEFAULT_OBJECT_RIGHTS = <<~XML
21
- <?xml version="1.0" encoding="UTF-8"?>
22
-
23
- <rightsMetadata>
24
- <access type="discover">
25
- <machine>
26
- <world/>
27
- </machine>
28
- </access>
29
- <access type="read">
30
- <machine>
31
- <world/>
32
- </machine>
33
- </access>
34
- <use>
35
- <human type="useAndReproduction"/>
36
- <human type="creativeCommons"/>
37
- <machine type="creativeCommons" uri=""/>
38
- <human type="openDataCommons"/>
39
- <machine type="openDataCommons" uri=""/>
40
- </use>
41
- <copyright>
42
- <human/>
43
- </copyright>
44
- </rightsMetadata>
45
- XML
46
-
47
- # An XML blob that is to be used temporarily until we model rights
48
- attribute :default_object_rights, Types::Strict::String.optional.default(DEFAULT_OBJECT_RIGHTS)
49
-
50
- # which workflow to start when registering (used by Web Archive apos to start wasCrawlPreassemblyWF)
51
- attribute :registration_workflow, Types::String.optional.default(nil)
52
-
53
- # TODO: Allowing hasAdminPolicy to be omittable for now (until rolled out to consumers),
54
- # but I think it's actually required for every Admin Policy
55
- attribute :hasAdminPolicy, Types::Strict::String.optional.default(nil)
56
- end
57
-
58
- class Identification < Struct
59
- end
60
-
61
- class Structural < Struct
62
- end
63
-
64
- include AdminPolicyAttributes
10
+ # example: item
11
+ attribute :type, Types::Strict::String.enum(*AdminPolicy::TYPES)
12
+ # example: druid:bc123df4567
65
13
  attribute :externalIdentifier, Types::Strict::String
14
+ attribute :label, Types::Strict::String
15
+ attribute :version, Types::Strict::Integer
16
+ attribute(:administrative, AdminPolicyAdministrative.default { AdminPolicyAdministrative.new })
17
+ attribute :description, Description.optional.meta(omittable: true)
18
+
19
+ def self.new(attributes = default_attributes, safe = false, validate = true, &block)
20
+ Validator.validate(self, attributes.with_indifferent_access) if validate
21
+ super(attributes, safe, &block)
22
+ end
66
23
  end
67
24
  end
68
25
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ class AdminPolicyAdministrative < Struct
6
+ attribute :defaultObjectRights, Types::Strict::String.default('<?xml version="1.0" encoding="UTF-8"?><rightsMetadata><access type="discover"><machine><world/></machine></access><access type="read"><machine><world/></machine></access><use><human type="useAndReproduction"/><human type="creativeCommons"/><machine type="creativeCommons" uri=""/><human type="openDataCommons"/><machine type="openDataCommons" uri=""/></use><copyright><human/></copyright></rightsMetadata>').meta(omittable: true)
7
+ attribute :registrationWorkflow, Types::Strict::String.meta(omittable: true)
8
+ attribute :hasAdminPolicy, Types::Strict::String.meta(omittable: true)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ class Administrative < Struct
6
+ # example: druid:bc123df4567
7
+ attribute :hasAdminPolicy, Types::Strict::String.meta(omittable: true)
8
+ attribute :releaseTags, Types::Strict::Array.of(ReleaseTag).meta(omittable: true)
9
+ # Administrative or Internal project this resource is a part of
10
+ # example: Google Books
11
+ attribute :partOfProject, Types::Strict::String.meta(omittable: true)
12
+ end
13
+ end
14
+ end
@@ -2,9 +2,12 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- # Metadata for a catalog link
6
5
  class CatalogLink < Struct
6
+ # Catalog that is the source of the linked record.
7
+ # example: symphony
7
8
  attribute :catalog, Types::Strict::String
9
+ # Record identifier that is unique within the context of the linked record's catalog.
10
+ # example: 11403803
8
11
  attribute :catalogRecordId, Types::Strict::String
9
12
  end
10
13
  end
@@ -2,43 +2,33 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- # A digital repository collection.
6
- # See http://sul-dlss.github.io/cocina-models/maps/Collection.json
7
5
  class Collection < Struct
8
6
  include Checkable
9
7
 
10
- TYPES = [
11
- Vocab.collection,
12
- Vocab.curated_collection,
13
- Vocab.exhibit,
14
- Vocab.series,
15
- Vocab.user_collection
16
- ].freeze
8
+ TYPES = ['http://cocina.sul.stanford.edu/models/collection.jsonld',
9
+ 'http://cocina.sul.stanford.edu/models/curated-collection.jsonld',
10
+ 'http://cocina.sul.stanford.edu/models/user-collection.jsonld',
11
+ 'http://cocina.sul.stanford.edu/models/exhibit.jsonld',
12
+ 'http://cocina.sul.stanford.edu/models/series.jsonld'].freeze
17
13
 
18
- # Subschema for access concerns
19
- class Access < Struct
20
- attribute :access, Types::String.default('dark')
21
- .enum('world', 'stanford', 'location-based', 'citation-only', 'dark')
22
- end
23
-
24
- # Subschema for administrative concerns
25
- class Administrative < Struct
26
- attribute :releaseTags, Types::Strict::Array.of(ReleaseTag).default([].freeze)
27
- # TODO: Allowing hasAdminPolicy to be omittable for now (until rolled out to consumers),
28
- # but I think it's actually required for every Collection
29
- attribute :hasAdminPolicy, Types::Strict::String.optional.default(nil)
30
- end
31
-
32
- # Identification sub-schema for the Collection
33
- class Identification < Struct
34
- attribute :catalogLinks, Types::Strict::Array.of(CatalogLink).meta(omittable: true)
35
- end
14
+ # The content type of the Collection. Selected from an established set of values.
15
+ # example: item
16
+ attribute :type, Types::Strict::String.enum(*Collection::TYPES)
17
+ # example: druid:bc123df4567
18
+ attribute :externalIdentifier, Types::Strict::String
19
+ # Primary processing label (can be same as title) for a Collection.
20
+ attribute :label, Types::Strict::String
21
+ # Version for the Collection within SDR.
22
+ attribute :version, Types::Strict::Integer
23
+ attribute(:access, Access.default { Access.new })
24
+ attribute :administrative, Administrative.optional.meta(omittable: true)
25
+ attribute :description, Description.optional.meta(omittable: true)
26
+ attribute :identification, CollectionIdentification.optional.meta(omittable: true)
36
27
 
37
- class Structural < Struct
28
+ def self.new(attributes = default_attributes, safe = false, validate = true, &block)
29
+ Validator.validate(self, attributes.with_indifferent_access) if validate
30
+ super(attributes, safe, &block)
38
31
  end
39
-
40
- include CollectionAttributes
41
- attribute :externalIdentifier, Types::Strict::String
42
32
  end
43
33
  end
44
34
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ class CollectionIdentification < Struct
6
+ attribute :catalogLinks, Types::Strict::Array.of(CatalogLink).meta(omittable: true)
7
+ end
8
+ end
9
+ end
@@ -2,15 +2,8 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- # Descriptive metadata. See http://sul-dlss.github.io/cocina-models/maps/Description.json
6
5
  class Description < Struct
7
- # Title element. See http://sul-dlss.github.io/cocina-models/maps/Title.json
8
- class Title < Struct
9
- attribute :primary, Types::Strict::Bool
10
- attribute :titleFull, Types::Strict::String
11
- end
12
-
13
- attribute :title, Types::Strict::Array.of(Title)
6
+ attribute :title, Types::Strict::Array.of(Title).default([].freeze)
14
7
  end
15
8
  end
16
9
  end
@@ -2,80 +2,44 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- # A digital repository object.
6
- # See http://sul-dlss.github.io/cocina-models/maps/DRO.json
7
5
  class DRO < Struct
8
6
  include Checkable
9
7
 
10
- TYPES = [
11
- Vocab.object,
12
- Vocab.three_dimensional,
13
- Vocab.agreement,
14
- Vocab.book,
15
- Vocab.document,
16
- Vocab.geo,
17
- Vocab.image,
18
- Vocab.page,
19
- Vocab.photograph,
20
- Vocab.manuscript,
21
- Vocab.map,
22
- Vocab.media,
23
- Vocab.track,
24
- Vocab.webarchive_binary,
25
- Vocab.webarchive_seed
26
- ].freeze
27
-
28
- # Subschema for access concerns
29
- class Access < Struct
30
- # Subschema for embargo concerns
31
- class Embargo < Struct
32
- attribute :releaseDate, Types::Params::DateTime
33
- attribute :access, Types::String.default('dark')
34
- .enum('world', 'stanford', 'location-based', 'citation-only', 'dark')
35
- attribute :useAndReproductionStatement, Types::Strict::String.meta(omittable: true)
36
- end
37
-
38
- attribute :access, Types::String.default('dark')
39
- .enum('world', 'stanford', 'location-based', 'citation-only', 'dark')
40
- attribute :copyright, Types::Strict::String.meta(omittable: true)
41
- attribute :embargo, Embargo.optional.meta(omittable: true)
42
- attribute :useAndReproductionStatement, Types::Strict::String.meta(omittable: true)
43
- end
44
-
45
- # Subschema for administrative concerns
46
- class Administrative < Struct
47
- # TODO: Allowing hasAdminPolicy to be omittable for now (until rolled out to consumers),
48
- # but I think it's actually required for every DRO
49
- attribute :hasAdminPolicy, Types::Strict::String.optional.default(nil)
50
- attribute :releaseTags, Types::Strict::Array.of(ReleaseTag).meta(omittable: true).default([].freeze)
51
- attribute :partOfProject, Types::Strict::String.meta(omittable: true)
52
- end
53
-
54
- # Identification sub-schema for the DRO
55
- class Identification < Struct
56
- attribute :sourceId, Types::Strict::String.meta(omittable: true)
57
- attribute :catalogLinks, Types::Strict::Array.of(CatalogLink).meta(omittable: true)
58
- end
59
-
60
- # Geographic sub-schema for the DRO
61
- class Geographic < Struct
62
- attribute :iso19139, Types::Strict::String
63
- end
64
-
65
- # Structural sub-schema for the DRO (uses FileSet, unlike RequestDRO which uses RequestFileSet)
66
- class Structural < Struct
67
- attribute :contains, Types::Strict::Array.of(FileSet).meta(omittable: true)
68
- attribute :hasAgreement, Types::Strict::String.meta(omittable: true)
69
- attribute :isMemberOf, Types::Strict::String.meta(omittable: true)
70
- attribute :hasMemberOrders, Types::Strict::Array.of(Sequence).meta(omittable: true)
71
- end
72
-
73
- include DroAttributes
8
+ TYPES = ['http://cocina.sul.stanford.edu/models/object.jsonld',
9
+ 'http://cocina.sul.stanford.edu/models/3d.jsonld',
10
+ 'http://cocina.sul.stanford.edu/models/agreement.jsonld',
11
+ 'http://cocina.sul.stanford.edu/models/book.jsonld',
12
+ 'http://cocina.sul.stanford.edu/models/document.jsonld',
13
+ 'http://cocina.sul.stanford.edu/models/geo.jsonld',
14
+ 'http://cocina.sul.stanford.edu/models/image.jsonld',
15
+ 'http://cocina.sul.stanford.edu/models/page.jsonld',
16
+ 'http://cocina.sul.stanford.edu/models/photograph.jsonld',
17
+ 'http://cocina.sul.stanford.edu/models/manuscript.jsonld',
18
+ 'http://cocina.sul.stanford.edu/models/map.jsonld',
19
+ 'http://cocina.sul.stanford.edu/models/media.jsonld',
20
+ 'http://cocina.sul.stanford.edu/models/track.jsonld',
21
+ 'http://cocina.sul.stanford.edu/models/webarchive-binary.jsonld',
22
+ 'http://cocina.sul.stanford.edu/models/webarchive-seed.jsonld'].freeze
23
+
24
+ # The content type of the DRO. Selected from an established set of values.
25
+ # example: item
26
+ attribute :type, Types::Strict::String.enum(*DRO::TYPES)
27
+ # example: druid:bc123df4567
74
28
  attribute :externalIdentifier, Types::Strict::String
75
- attribute(:structural, Structural.default { Structural.new })
76
-
77
- def image?
78
- type == Vocab.image
29
+ # Primary processing label (can be same as title) for a DRO.
30
+ attribute :label, Types::Strict::String
31
+ # Version for the DRO within SDR.
32
+ attribute :version, Types::Strict::Integer
33
+ attribute(:access, DROAccess.default { DROAccess.new })
34
+ attribute :administrative, Administrative.optional.meta(omittable: true)
35
+ attribute :description, Description.optional.meta(omittable: true)
36
+ attribute :identification, Identification.optional.meta(omittable: true)
37
+ attribute :structural, DROStructural.optional.meta(omittable: true)
38
+ attribute :geographic, Geographic.optional.meta(omittable: true)
39
+
40
+ def self.new(attributes = default_attributes, safe = false, validate = true, &block)
41
+ Validator.validate(self, attributes.with_indifferent_access) if validate
42
+ super(attributes, safe, &block)
79
43
  end
80
44
  end
81
45
  end