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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +51 -0
- data/.github/pull_request_template.md +8 -1
- data/.rubocop.yml +14 -3
- data/README.md +21 -13
- data/cocina-models.gemspec +6 -1
- data/docs/index.html +20 -0
- data/docs/maps/DRO.json +1 -1
- data/exe/generator +9 -0
- data/lib/cocina/generator.rb +7 -0
- data/lib/cocina/generator/generator.rb +80 -0
- data/lib/cocina/generator/schema.rb +105 -0
- data/lib/cocina/generator/schema_array.rb +24 -0
- data/lib/cocina/generator/schema_base.rb +71 -0
- data/lib/cocina/generator/schema_ref.rb +16 -0
- data/lib/cocina/generator/schema_value.rb +38 -0
- data/lib/cocina/generator/vocab.rb +63 -0
- data/lib/cocina/models.rb +54 -25
- data/lib/cocina/models/access.rb +14 -0
- data/lib/cocina/models/admin_policy.rb +13 -56
- data/lib/cocina/models/admin_policy_administrative.rb +11 -0
- data/lib/cocina/models/administrative.rb +14 -0
- data/lib/cocina/models/applies_to.rb +9 -0
- data/lib/cocina/models/catalog_link.rb +4 -1
- data/lib/cocina/models/collection.rb +21 -31
- data/lib/cocina/models/collection_identification.rb +9 -0
- data/lib/cocina/models/contributor.rb +14 -0
- data/lib/cocina/models/description.rb +16 -7
- data/lib/cocina/models/descriptive_admin_metadata.rb +12 -0
- data/lib/cocina/models/descriptive_basic_value.rb +21 -0
- data/lib/cocina/models/descriptive_structured_value.rb +9 -0
- data/lib/cocina/models/descriptive_value.rb +23 -0
- data/lib/cocina/models/descriptive_value_required.rb +23 -0
- data/lib/cocina/models/dro.rb +34 -70
- data/lib/cocina/models/dro_access.rb +22 -0
- data/lib/cocina/models/dro_structural.rb +14 -0
- data/lib/cocina/models/embargo.rb +16 -0
- data/lib/cocina/models/event.rb +15 -0
- data/lib/cocina/models/file.rb +20 -36
- data/lib/cocina/models/file_administrative.rb +10 -0
- data/lib/cocina/models/file_set.rb +8 -15
- data/lib/cocina/models/file_set_structural.rb +9 -0
- data/lib/cocina/models/geographic.rb +10 -0
- data/lib/cocina/models/identification.rb +11 -0
- data/lib/cocina/models/message_digest.rb +17 -0
- data/lib/cocina/models/presentation.rb +12 -0
- data/lib/cocina/models/release_tag.rb +12 -7
- data/lib/cocina/models/request_admin_policy.rb +15 -3
- data/lib/cocina/models/request_collection.rb +21 -4
- data/lib/cocina/models/request_dro.rb +32 -11
- data/lib/cocina/models/request_dro_structural.rb +13 -0
- data/lib/cocina/models/request_file.rb +15 -6
- data/lib/cocina/models/request_file_set.rb +7 -9
- data/lib/cocina/models/request_file_set_structural.rb +9 -0
- data/lib/cocina/models/request_identification.rb +11 -0
- data/lib/cocina/models/sequence.rb +3 -5
- data/lib/cocina/models/source.rb +14 -0
- data/lib/cocina/models/validator.rb +28 -0
- data/lib/cocina/models/version.rb +1 -1
- data/lib/cocina/models/vocab.rb +45 -60
- data/openapi.yml +1003 -0
- metadata +116 -19
- data/.travis.yml +0 -23
- data/docs/README.md +0 -9
- data/docs/_config.yml +0 -1
- data/docs/meta.json +0 -9
- data/docs/schema.json +0 -1654
- data/docs/schema.md +0 -268
- data/lib/cocina/models/admin_policy_attributes.rb +0 -21
- data/lib/cocina/models/collection_attributes.rb +0 -22
- data/lib/cocina/models/dro_attributes.rb +0 -22
- data/lib/cocina/models/file_attributes.rb +0 -25
- data/lib/cocina/models/file_set_attributes.rb +0 -16
- 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
|
data/lib/cocina/models.rb
CHANGED
@@ -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
|
-
# @
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
#
|
14
|
-
|
15
|
-
|
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
|