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.
- checksums.yaml +4 -4
- data/.rubocop.yml +10 -3
- data/README.md +11 -0
- data/cocina-models.gemspec +5 -0
- data/exe/generator +9 -0
- data/lib/cocina/generator.rb +18 -0
- data/lib/cocina/generator/generator.rb +80 -0
- data/lib/cocina/generator/schema.rb +84 -0
- data/lib/cocina/generator/schema_array.rb +20 -0
- data/lib/cocina/generator/schema_base.rb +49 -0
- data/lib/cocina/generator/schema_ref.rb +16 -0
- data/lib/cocina/generator/schema_value.rb +60 -0
- data/lib/cocina/generator/vocab.rb +63 -0
- data/lib/cocina/models.rb +51 -25
- data/lib/cocina/models/access.rb +10 -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/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/description.rb +1 -8
- data/lib/cocina/models/dro.rb +34 -70
- data/lib/cocina/models/dro_access.rb +16 -0
- data/lib/cocina/models/dro_structural.rb +14 -0
- data/lib/cocina/models/embargo.rb +16 -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/sequence.rb +2 -5
- data/lib/cocina/models/title.rb +12 -0
- data/lib/cocina/models/validator.rb +22 -0
- data/lib/cocina/models/version.rb +1 -1
- data/lib/cocina/models/vocab.rb +45 -60
- data/openapi.yml +695 -0
- metadata +101 -9
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae254699614b4355dea34dab3e0ef324123b21d30d592c24ceffbad5fed3515e
|
4
|
+
data.tar.gz: da8eecbff7bec360ed56445d61fc558b4a18684df9f0f92bc4a98668e2d16dac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c6c2e1beb7cd1b28d73bee2641be223497e18764bbffae9953fde54995336ae970be70e8d1ac31fa96f131ad9c17a02b2a5ef7b9e9beb098ecf9925899333ad
|
7
|
+
data.tar.gz: 53246fecec8a7e4516aa3fef318dddfa644158678f4894d1eeb1285d0c247b6a2bdafd68a491ae18b19e41fa5a195bc2f078ba303adb9f83f37b26c4121585cf
|
data/.rubocop.yml
CHANGED
@@ -4,13 +4,16 @@ inherit_from: .rubocop_todo.yml
|
|
4
4
|
require:
|
5
5
|
- rubocop-rspec
|
6
6
|
|
7
|
+
Metrics/LineLength:
|
8
|
+
Max: 114
|
9
|
+
Exclude:
|
10
|
+
- lib/cocina/models/*
|
11
|
+
|
7
12
|
Metrics/BlockLength:
|
8
13
|
Exclude:
|
14
|
+
- cocina-models.gemspec
|
9
15
|
- spec/cocina/**/*
|
10
16
|
|
11
|
-
Metrics/LineLength:
|
12
|
-
Max: 114
|
13
|
-
|
14
17
|
Metrics/MethodLength:
|
15
18
|
Max: 14
|
16
19
|
|
@@ -19,3 +22,7 @@ RSpec/MultipleExpectations:
|
|
19
22
|
|
20
23
|
RSpec/ExampleLength:
|
21
24
|
Max: 18
|
25
|
+
|
26
|
+
Style/Documentation:
|
27
|
+
Exclude:
|
28
|
+
- lib/cocina/models/*
|
data/README.md
CHANGED
@@ -11,6 +11,17 @@ It provides a way for consumers to validate objects against models using dry-str
|
|
11
11
|
|
12
12
|
This is a work in progress that will ultimately implement the full [COCINA data model](http://sul-dlss.github.io/cocina-models/). See also [architecture documentation](https://sul-dlss.github.io/taco-truck/COCINA.html#cocina-data-models--shapes).
|
13
13
|
|
14
|
+
## Generate models from openapi.yml
|
15
|
+
|
16
|
+
### All
|
17
|
+
```
|
18
|
+
exe/generator generate
|
19
|
+
```
|
20
|
+
|
21
|
+
### Single model
|
22
|
+
```
|
23
|
+
exe/generator generate_schema DRO
|
24
|
+
```
|
14
25
|
|
15
26
|
## Generate Documentation
|
16
27
|
|
data/cocina-models.gemspec
CHANGED
@@ -23,11 +23,16 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
24
|
spec.require_paths = ['lib']
|
25
25
|
|
26
|
+
spec.add_dependency 'activesupport'
|
26
27
|
spec.add_dependency 'dry-struct', '~> 1.0'
|
27
28
|
spec.add_dependency 'dry-types', '~> 1.1'
|
29
|
+
spec.add_dependency 'openapi3_parser' # Parsing openapi doc
|
30
|
+
spec.add_dependency 'openapi_parser' # Validating openapi requests
|
31
|
+
spec.add_dependency 'thor'
|
28
32
|
spec.add_dependency 'zeitwerk', '~> 2.1'
|
29
33
|
|
30
34
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
35
|
+
spec.add_development_dependency 'committee'
|
31
36
|
spec.add_development_dependency 'rake', '~> 12.0'
|
32
37
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
33
38
|
spec.add_development_dependency 'rubocop', '~> 0.74.0'
|
data/exe/generator
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zeitwerk'
|
4
|
+
require 'yaml'
|
5
|
+
require 'active_support/core_ext/string'
|
6
|
+
require 'thor'
|
7
|
+
require 'openapi3_parser'
|
8
|
+
require 'byebug'
|
9
|
+
|
10
|
+
loader = Zeitwerk::Loader.new
|
11
|
+
loader.push_dir(File.absolute_path("#{__FILE__}/../.."))
|
12
|
+
loader.setup
|
13
|
+
|
14
|
+
module Cocina
|
15
|
+
# Module for generating Cocina models from openapi.
|
16
|
+
module Generator
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Generator
|
5
|
+
# Class for generating Cocina models from openapi.
|
6
|
+
class Generator < Thor
|
7
|
+
include Thor::Actions
|
8
|
+
|
9
|
+
class_option :openapi, desc: 'Path of openapi.yml', default: 'openapi.yml'
|
10
|
+
class_option :output, desc: 'Path for output', default: 'lib/cocina/models'
|
11
|
+
|
12
|
+
def self.source_root
|
13
|
+
File.dirname(__FILE__)
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'generate', 'generate for all schemas'
|
17
|
+
def generate
|
18
|
+
clean_output
|
19
|
+
|
20
|
+
schemas.keys.each do |schema_name|
|
21
|
+
schema = schema_for(schema_name)
|
22
|
+
generate_for(schema) if schema
|
23
|
+
end
|
24
|
+
|
25
|
+
generate_vocab
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'generate_schema SCHEMA_NAME', 'generate for SCHEMA_NAME'
|
29
|
+
def generate_schema(schema_name)
|
30
|
+
schema = schema_for(schema_name)
|
31
|
+
raise 'Cannot generate' if schema.nil?
|
32
|
+
|
33
|
+
FileUtils.mkdir_p(options[:output])
|
34
|
+
|
35
|
+
generate_for(schema)
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'generate_vocab', 'generate vocab'
|
39
|
+
def generate_vocab
|
40
|
+
vocab = Vocab.new(schemas)
|
41
|
+
filepath = "#{options[:output]}/#{vocab.filename}"
|
42
|
+
FileUtils.rm_f(filepath)
|
43
|
+
|
44
|
+
create_file filepath, vocab.generate
|
45
|
+
run("rubocop -a #{filepath}")
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def schemas
|
51
|
+
@schemas ||= Openapi3Parser.load_file(options[:openapi]).components.schemas
|
52
|
+
end
|
53
|
+
|
54
|
+
def schema_for(schema_name)
|
55
|
+
schema_doc = schemas[schema_name]
|
56
|
+
return nil if schema_doc.nil? || schema_doc.type != 'object'
|
57
|
+
|
58
|
+
Schema.new(schema_doc)
|
59
|
+
end
|
60
|
+
|
61
|
+
def generate_for(schema)
|
62
|
+
filepath = "#{options[:output]}/#{schema.filename}"
|
63
|
+
FileUtils.rm_f(filepath)
|
64
|
+
|
65
|
+
create_file filepath, schema.generate
|
66
|
+
run("rubocop -a #{filepath}")
|
67
|
+
end
|
68
|
+
|
69
|
+
def clean_output
|
70
|
+
FileUtils.mkdir_p(options[:output])
|
71
|
+
files = Dir.glob("#{options[:output]}/*.rb")
|
72
|
+
# Leave alone
|
73
|
+
files.delete("#{options[:output]}/version.rb")
|
74
|
+
files.delete("#{options[:output]}/checkable.rb")
|
75
|
+
files.delete("#{options[:output]}/validator.rb")
|
76
|
+
FileUtils.rm_f(files)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Generator
|
5
|
+
# Class for generating from an openapi schema
|
6
|
+
class Schema < SchemaBase
|
7
|
+
def schema_properties
|
8
|
+
@schema_properties ||= schema_doc.properties.map do |key, properties_doc|
|
9
|
+
property_class_for(properties_doc).new(properties_doc,
|
10
|
+
key: key,
|
11
|
+
required: schema_doc.requires?(properties_doc),
|
12
|
+
parent: self)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate
|
17
|
+
<<~RUBY
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
module Cocina
|
21
|
+
module Models
|
22
|
+
class #{name} < Struct
|
23
|
+
|
24
|
+
#{types}
|
25
|
+
|
26
|
+
#{model_attributes}
|
27
|
+
|
28
|
+
#{validate}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
RUBY
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def property_class_for(properties_doc)
|
38
|
+
case properties_doc.type
|
39
|
+
when 'object'
|
40
|
+
# As a useful simplification, all objects must be references to schemas.
|
41
|
+
raise "Must use a reference for #{schema_doc.inspect}" unless properties_doc.name
|
42
|
+
|
43
|
+
SchemaRef
|
44
|
+
when 'array'
|
45
|
+
SchemaArray
|
46
|
+
else
|
47
|
+
SchemaValue
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def model_attributes
|
52
|
+
schema_properties.map(&:generate).join("\n")
|
53
|
+
end
|
54
|
+
|
55
|
+
def types
|
56
|
+
type_properties_doc = schema_doc.properties['type']
|
57
|
+
return '' if type_properties_doc.nil?
|
58
|
+
|
59
|
+
types_list = type_properties_doc.enum.map { |item| "'#{item}'" }.join(",\n ")
|
60
|
+
|
61
|
+
<<~RUBY
|
62
|
+
include Checkable
|
63
|
+
|
64
|
+
TYPES = [#{types_list}].freeze
|
65
|
+
RUBY
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate
|
69
|
+
return '' unless validatable?
|
70
|
+
|
71
|
+
<<~RUBY
|
72
|
+
def self.new(attributes = default_attributes, safe = false, validate = true, &block)
|
73
|
+
Validator.validate(self, attributes.with_indifferent_access) if validate
|
74
|
+
super(attributes, safe, &block)
|
75
|
+
end
|
76
|
+
RUBY
|
77
|
+
end
|
78
|
+
|
79
|
+
def validatable?
|
80
|
+
!schema_doc.node_context.document.paths["/validate/#{schema_doc.name}"].nil?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cocina
|
4
|
+
module Generator
|
5
|
+
# Class for generating from an openapi array
|
6
|
+
class SchemaArray < SchemaBase
|
7
|
+
def generate
|
8
|
+
"attribute :#{name.camelize(:lower)}, Types::Strict::Array.of(#{schema_doc.items.name})#{omittable}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def omittable
|
12
|
+
if required
|
13
|
+
'.default([].freeze)'
|
14
|
+
else
|
15
|
+
'.meta(omittable: true)'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,49 @@
|
|
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
|
+
end
|
48
|
+
end
|
49
|
+
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,60 @@
|
|
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}#{default}#{enum}#{omittable}"
|
10
|
+
end
|
11
|
+
# rubocop:enable Metrics/LineLength
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def dry_datatype
|
16
|
+
case schema_doc.type
|
17
|
+
when 'integer'
|
18
|
+
'Strict::Integer'
|
19
|
+
when 'string'
|
20
|
+
string_dry_datatype
|
21
|
+
when 'boolean'
|
22
|
+
'Strict::Bool'
|
23
|
+
else
|
24
|
+
raise "#{schema_doc.type} not supported"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def string_dry_datatype
|
29
|
+
case schema_doc.format
|
30
|
+
when 'date-time'
|
31
|
+
'Params::DateTime'
|
32
|
+
else
|
33
|
+
'Strict::String'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def enum
|
38
|
+
return '' unless schema_doc.enum
|
39
|
+
|
40
|
+
items = use_types? ? "*#{parent.name}::TYPES" : schema_doc.enum.map { |item| quote(item) }.join(', ')
|
41
|
+
|
42
|
+
".enum(#{items})"
|
43
|
+
end
|
44
|
+
|
45
|
+
def use_types?
|
46
|
+
parent.is_a?(Schema) && key == 'type'
|
47
|
+
end
|
48
|
+
|
49
|
+
def default
|
50
|
+
# If type is boolean and default is false, erroneously getting a nil.
|
51
|
+
# Assuming that if required, then default is false.
|
52
|
+
default = schema_doc.default
|
53
|
+
default = false if default.nil? && schema_doc.type == 'boolean' && required
|
54
|
+
return '' if default.nil?
|
55
|
+
|
56
|
+
".default(#{quote(default)})"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
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
|