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.
- 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
|