cocina-models 0.29.0 → 0.33.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +51 -0
  3. data/.github/pull_request_template.md +8 -1
  4. data/.rubocop.yml +14 -3
  5. data/README.md +21 -13
  6. data/cocina-models.gemspec +6 -1
  7. data/docs/index.html +20 -0
  8. data/docs/maps/DRO.json +1 -1
  9. data/exe/generator +9 -0
  10. data/lib/cocina/generator.rb +7 -0
  11. data/lib/cocina/generator/generator.rb +80 -0
  12. data/lib/cocina/generator/schema.rb +105 -0
  13. data/lib/cocina/generator/schema_array.rb +24 -0
  14. data/lib/cocina/generator/schema_base.rb +71 -0
  15. data/lib/cocina/generator/schema_ref.rb +16 -0
  16. data/lib/cocina/generator/schema_value.rb +38 -0
  17. data/lib/cocina/generator/vocab.rb +63 -0
  18. data/lib/cocina/models.rb +54 -25
  19. data/lib/cocina/models/access.rb +14 -0
  20. data/lib/cocina/models/admin_policy.rb +13 -56
  21. data/lib/cocina/models/admin_policy_administrative.rb +11 -0
  22. data/lib/cocina/models/administrative.rb +14 -0
  23. data/lib/cocina/models/applies_to.rb +9 -0
  24. data/lib/cocina/models/catalog_link.rb +4 -1
  25. data/lib/cocina/models/collection.rb +21 -31
  26. data/lib/cocina/models/collection_identification.rb +9 -0
  27. data/lib/cocina/models/contributor.rb +14 -0
  28. data/lib/cocina/models/description.rb +16 -7
  29. data/lib/cocina/models/descriptive_admin_metadata.rb +12 -0
  30. data/lib/cocina/models/descriptive_basic_value.rb +21 -0
  31. data/lib/cocina/models/descriptive_structured_value.rb +9 -0
  32. data/lib/cocina/models/descriptive_value.rb +23 -0
  33. data/lib/cocina/models/descriptive_value_required.rb +23 -0
  34. data/lib/cocina/models/dro.rb +34 -70
  35. data/lib/cocina/models/dro_access.rb +22 -0
  36. data/lib/cocina/models/dro_structural.rb +14 -0
  37. data/lib/cocina/models/embargo.rb +16 -0
  38. data/lib/cocina/models/event.rb +15 -0
  39. data/lib/cocina/models/file.rb +20 -36
  40. data/lib/cocina/models/file_administrative.rb +10 -0
  41. data/lib/cocina/models/file_set.rb +8 -15
  42. data/lib/cocina/models/file_set_structural.rb +9 -0
  43. data/lib/cocina/models/geographic.rb +10 -0
  44. data/lib/cocina/models/identification.rb +11 -0
  45. data/lib/cocina/models/message_digest.rb +17 -0
  46. data/lib/cocina/models/presentation.rb +12 -0
  47. data/lib/cocina/models/release_tag.rb +12 -7
  48. data/lib/cocina/models/request_admin_policy.rb +15 -3
  49. data/lib/cocina/models/request_collection.rb +21 -4
  50. data/lib/cocina/models/request_dro.rb +32 -11
  51. data/lib/cocina/models/request_dro_structural.rb +13 -0
  52. data/lib/cocina/models/request_file.rb +15 -6
  53. data/lib/cocina/models/request_file_set.rb +7 -9
  54. data/lib/cocina/models/request_file_set_structural.rb +9 -0
  55. data/lib/cocina/models/request_identification.rb +11 -0
  56. data/lib/cocina/models/sequence.rb +3 -5
  57. data/lib/cocina/models/source.rb +14 -0
  58. data/lib/cocina/models/validator.rb +28 -0
  59. data/lib/cocina/models/version.rb +1 -1
  60. data/lib/cocina/models/vocab.rb +45 -60
  61. data/openapi.yml +1003 -0
  62. metadata +116 -19
  63. data/.travis.yml +0 -23
  64. data/docs/README.md +0 -9
  65. data/docs/_config.yml +0 -1
  66. data/docs/meta.json +0 -9
  67. data/docs/schema.json +0 -1654
  68. data/docs/schema.md +0 -268
  69. data/lib/cocina/models/admin_policy_attributes.rb +0 -21
  70. data/lib/cocina/models/collection_attributes.rb +0 -22
  71. data/lib/cocina/models/dro_attributes.rb +0 -22
  72. data/lib/cocina/models/file_attributes.rb +0 -25
  73. data/lib/cocina/models/file_set_attributes.rb +0 -16
  74. data/lib/cocina/models/types.rb +0 -10
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Generator
5
+ # Base class for generating from openapi
6
+ class SchemaBase
7
+ attr_reader :schema_doc, :key, :required, :parent
8
+
9
+ def initialize(schema_doc, key: nil, required: false, parent: nil)
10
+ @schema_doc = schema_doc
11
+ @key = key
12
+ @required = required
13
+ @parent = parent
14
+ end
15
+
16
+ def filename
17
+ "#{name.underscore}.rb"
18
+ end
19
+
20
+ def name
21
+ key || schema_doc.name
22
+ end
23
+
24
+ def omittable
25
+ return '' if required
26
+
27
+ '.meta(omittable: true)'
28
+ end
29
+
30
+ def quote(item)
31
+ return item unless schema_doc.type == 'string'
32
+
33
+ "'#{item}'"
34
+ end
35
+
36
+ def description
37
+ return '' unless schema_doc.description
38
+
39
+ "# #{schema_doc.description}\n"
40
+ end
41
+
42
+ def example
43
+ return '' unless schema_doc.example
44
+
45
+ "# example: #{schema_doc.example}\n"
46
+ end
47
+
48
+ def dry_datatype(doc)
49
+ case doc.type
50
+ when 'integer'
51
+ 'Strict::Integer'
52
+ when 'string'
53
+ string_dry_datatype(doc)
54
+ when 'boolean'
55
+ 'Strict::Bool'
56
+ else
57
+ raise "#{schema_doc.type} not supported"
58
+ end
59
+ end
60
+
61
+ def string_dry_datatype(doc)
62
+ case doc.format
63
+ when 'date-time'
64
+ 'Params::DateTime'
65
+ else
66
+ 'Strict::String'
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Generator
5
+ # Class for generating from an openapi reference
6
+ class SchemaRef < SchemaBase
7
+ def generate
8
+ if required
9
+ "attribute(:#{name.camelize(:lower)}, #{schema_doc.name}.default { #{schema_doc.name}.new })"
10
+ else
11
+ "attribute :#{name.camelize(:lower)}, #{schema_doc.name}.optional.meta(omittable: true)"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Generator
5
+ # Class for generating from an openapi value
6
+ class SchemaValue < SchemaBase
7
+ # rubocop:disable Metrics/LineLength
8
+ def generate
9
+ "#{description}#{example}attribute :#{name.camelize(:lower)}, Types::#{dry_datatype(schema_doc)}#{default}#{enum}#{omittable}"
10
+ end
11
+ # rubocop:enable Metrics/LineLength
12
+
13
+ private
14
+
15
+ def enum
16
+ return '' unless schema_doc.enum
17
+
18
+ items = use_types? ? "*#{parent.name}::TYPES" : schema_doc.enum.map { |item| quote(item) }.join(', ')
19
+
20
+ ".enum(#{items})"
21
+ end
22
+
23
+ def use_types?
24
+ parent.is_a?(Schema) && key == 'type'
25
+ end
26
+
27
+ def default
28
+ # If type is boolean and default is false, erroneously getting a nil.
29
+ # Assuming that if required, then default is false.
30
+ default = schema_doc.default
31
+ default = false if default.nil? && schema_doc.type == 'boolean' && required
32
+ return '' if default.nil?
33
+
34
+ ".default(#{quote(default)})"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Generator
5
+ # Class for generating a vocab
6
+ class Vocab
7
+ def initialize(schemas)
8
+ @schemas = schemas
9
+ end
10
+
11
+ def filename
12
+ 'vocab.rb'
13
+ end
14
+
15
+ def generate
16
+ <<~RUBY
17
+ # frozen_string_literal: true
18
+
19
+ module Cocina
20
+ module Models
21
+ # A digital repository object. See http://sul-dlss.github.io/cocina-models/maps/DRO.json
22
+ class Vocab
23
+
24
+ #{vocab_methods}
25
+
26
+ end
27
+ end
28
+ end
29
+ RUBY
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :schemas
35
+
36
+ # rubocop:disable Style/MultilineBlockChain
37
+ def vocabs
38
+ schemas.values.map do |schema|
39
+ type_property = schema.properties['type']
40
+ type_property.nil? ? [] : type_property.enum.to_a
41
+ end
42
+ .flatten
43
+ .uniq
44
+ .sort
45
+ .filter { |vocab| vocab.start_with?('http://cocina.sul.stanford.edu/models') }
46
+ end
47
+ # rubocop:enable Style/MultilineBlockChain
48
+
49
+ def vocab_methods
50
+ # Note special handling of 3d
51
+ vocabs.map do |vocab|
52
+ name = vocab[38, vocab.size - 45].gsub('-', '_').gsub('3d', 'three_dimensional')
53
+ <<~RUBY
54
+ def self.#{name}
55
+ "#{vocab}"
56
+ end
57
+
58
+ RUBY
59
+ end.join("\n")
60
+ end
61
+ end
62
+ end
63
+ end
@@ -5,23 +5,37 @@ require 'zeitwerk'
5
5
  require 'dry-struct'
6
6
  require 'dry-types'
7
7
  require 'json'
8
+ require 'yaml'
9
+ require 'openapi_parser'
10
+ require 'openapi3_parser'
11
+ require 'active_support/core_ext/hash/indifferent_access'
12
+ require 'active_support/core_ext/string'
13
+ require 'thor'
8
14
 
9
15
  # Help Zeitwerk find some of our classes
10
16
  class CocinaModelsInflector < Zeitwerk::Inflector
17
+ # rubocop:disable Metrics/CyclomaticComplexity
18
+ # rubocop:disable Metrics/MethodLength
11
19
  def camelize(basename, _abspath)
12
20
  case basename
13
21
  when 'dro'
14
22
  'DRO'
15
- when 'dro_builder'
16
- 'DROBuilder'
17
23
  when 'request_dro'
18
24
  'RequestDRO'
25
+ when 'dro_access'
26
+ 'DROAccess'
27
+ when 'dro_structural'
28
+ 'DROStructural'
29
+ when 'request_dro_structural'
30
+ 'RequestDROStructural'
19
31
  when 'version'
20
32
  'VERSION'
21
33
  else
22
34
  super
23
35
  end
24
36
  end
37
+ # rubocop:enable Metrics/CyclomaticComplexity
38
+ # rubocop:enable Metrics/MethodLength
25
39
  end
26
40
 
27
41
  loader = Zeitwerk::Loader.new
@@ -36,43 +50,58 @@ module Cocina
36
50
  # Raised when the type attribute is not valid.
37
51
  class UnknownTypeError < Error; end
38
52
 
53
+ # Raised when an error occurs validating against openapi.
54
+ class ValidationError < Error; end
55
+
39
56
  # Base class for Cocina Structs
40
57
  class Struct < Dry::Struct
41
58
  transform_keys(&:to_sym)
59
+ schema schema.strict
60
+ end
61
+
62
+ # DRY Types
63
+ module Types
64
+ include Dry.Types()
42
65
  end
43
66
 
44
67
  # @param [Hash] dyn a ruby hash representation of the JSON serialization of a collection or DRO
45
- # @return [DRO,Collection]
68
+ # @param [boolean] validate
69
+ # @return [DRO,Collection,AdminPolicy]
46
70
  # @raises [UnknownTypeError] if a valid type is not found in the data
47
71
  # @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
72
+ # @raises [ValidationError] if hash representation fails openapi validation
73
+ def self.build(dyn, validate: true)
74
+ clazz = case dyn.fetch('type')
75
+ when *DRO::TYPES
76
+ DRO
77
+ when *Collection::TYPES
78
+ Collection
79
+ when *AdminPolicy::TYPES
80
+ AdminPolicy
81
+ else
82
+ raise UnknownTypeError, "Unknown type: '#{dyn.fetch('type')}'"
83
+ end
84
+ clazz.new(dyn, false, validate)
59
85
  end
60
86
 
61
87
  # @param [Hash] dyn a ruby hash representation of the JSON serialization of a request for a Collection or DRO
88
+ # @param [boolean] validate
62
89
  # @return [RequestDRO,RequestCollection,RequestAdminPolicy]
63
90
  # @raises [UnknownTypeError] if a valid type is not found in the data
64
91
  # @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
92
+ # @raises [ValidationError] if hash representation fails openapi validation
93
+ def self.build_request(dyn, validate: true)
94
+ clazz = case dyn.fetch('type')
95
+ when *DRO::TYPES
96
+ RequestDRO
97
+ when *Collection::TYPES
98
+ RequestCollection
99
+ when *AdminPolicy::TYPES
100
+ RequestAdminPolicy
101
+ else
102
+ raise UnknownTypeError, "Unknown type: '#{dyn.fetch('type')}'"
103
+ end
104
+ clazz.new(dyn, false, validate)
76
105
  end
77
106
  end
78
107
  end
@@ -0,0 +1,14 @@
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
+ # Download access level for a file
9
+ attribute :download, Types::Strict::String.default('none').enum('world', 'stanford', 'location-based', 'none').meta(omittable: true)
10
+ # If access is "location-based", which location should have access.
11
+ attribute :readLocation, Types::Strict::String.enum('spec', 'music', 'ars', 'art', 'hoover', 'm&m').meta(omittable: true)
12
+ end
13
+ end
14
+ 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 && name
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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ class AppliesTo < Struct
6
+ attribute :appliesTo, Types::Strict::Array.of(DescriptiveBasicValue).meta(omittable: true)
7
+ end
8
+ end
9
+ end