cocina-models 0.29.0 → 0.30.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 (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