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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa8dd7285b70bcc1a81fd099ada06d39ceba9f24900076deec73dd46573df2f4
|
4
|
+
data.tar.gz: c196659cf0ce69aee69ffc3472235d68d9e4eb0503ed70aa5c79f517c8412498
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a001d343d4233b377aa0c666383ce2f70abb36f3fc05169f007fc7f1b6f8e9784f5ff67f204255a585b1f4e9ddd6c3d624a4baf0057b78607c73b7df93e3b173
|
7
|
+
data.tar.gz: cb9db638c1ffaad358e05bc097c5fc9ec44a3df56bb4b13418f417daf2a71879561885a9e26adfcaf37e37bdf3542fca9055340816f2253b8f5f9f4c69b0eef0
|
@@ -0,0 +1,51 @@
|
|
1
|
+
version: 2.1
|
2
|
+
executors:
|
3
|
+
docker-publisher:
|
4
|
+
docker:
|
5
|
+
- image: circleci/buildpack-deps:stretch
|
6
|
+
jobs:
|
7
|
+
test:
|
8
|
+
docker:
|
9
|
+
- image: circleci/ruby:2.7.0-node
|
10
|
+
steps:
|
11
|
+
- checkout
|
12
|
+
- run:
|
13
|
+
name: install bundler
|
14
|
+
command: gem install bundler -v 2.1.4
|
15
|
+
- run:
|
16
|
+
name: Install gem dependencies
|
17
|
+
command: bundle check || bundle install
|
18
|
+
- run:
|
19
|
+
name: Lint using rubocop
|
20
|
+
command: bundle exec rubocop
|
21
|
+
- run:
|
22
|
+
name: Setup Code Climate test-reporter
|
23
|
+
command: |
|
24
|
+
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
25
|
+
chmod +x ./cc-test-reporter
|
26
|
+
./cc-test-reporter before-build
|
27
|
+
- run:
|
28
|
+
name: Run RSpec test suite
|
29
|
+
command: bundle exec rspec
|
30
|
+
- run:
|
31
|
+
name: upload test coverage report to Code Climate
|
32
|
+
command: ./cc-test-reporter after-build --coverage-input-type simplecov --exit-code $?
|
33
|
+
- run:
|
34
|
+
name: Validate API specification
|
35
|
+
command: |
|
36
|
+
sudo npm install -g openapi-enforcer-cli
|
37
|
+
result=$(openapi-enforcer validate openapi.yml)
|
38
|
+
[[ $result =~ "Document is valid" ]] && {
|
39
|
+
echo "Validation good"
|
40
|
+
exit 0
|
41
|
+
} || {
|
42
|
+
echo $result
|
43
|
+
exit 1
|
44
|
+
}
|
45
|
+
|
46
|
+
workflows:
|
47
|
+
version: 2
|
48
|
+
|
49
|
+
test:
|
50
|
+
jobs:
|
51
|
+
- test
|
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,11 @@ RSpec/MultipleExpectations:
|
|
19
22
|
|
20
23
|
RSpec/ExampleLength:
|
21
24
|
Max: 18
|
25
|
+
Exclude:
|
26
|
+
- spec/cocina/models/description_spec.rb
|
27
|
+
- spec/cocina/models/dro_shared_examples.rb
|
28
|
+
|
29
|
+
|
30
|
+
Style/Documentation:
|
31
|
+
Exclude:
|
32
|
+
- lib/cocina/models/*
|
data/README.md
CHANGED
@@ -1,31 +1,39 @@
|
|
1
|
-
[![
|
1
|
+
[![CircleCI](https://circleci.com/gh/sul-dlss/cocina-models.svg?style=svg)](https://circleci.com/gh/sul-dlss/cocina-models)
|
2
2
|
[![Test Coverage](https://api.codeclimate.com/v1/badges/472273351516ac01dce1/test_coverage)](https://codeclimate.com/github/sul-dlss/cocina-models/test_coverage)
|
3
3
|
[![Maintainability](https://api.codeclimate.com/v1/badges/472273351516ac01dce1/maintainability)](https://codeclimate.com/github/sul-dlss/cocina-models/maintainability)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/cocina-models.svg)](https://badge.fury.io/rb/cocina-models)
|
5
|
+
[![OpenAPI Validator](http://validator.swagger.io/validator?url=https://raw.githubusercontent.com/sul-dlss/cocina-models/master/openapi.yml)](http://validator.swagger.io/validator/debug?url=https://raw.githubusercontent.com/sul-dlss/cocina-models/master/openapi.yml)
|
5
6
|
|
6
7
|
# Cocina::Models
|
7
8
|
|
8
9
|
This is a Ruby implementation of the SDR data model (named "COCINA"). The data being modeled includes digital repository objects.
|
9
10
|
|
10
|
-
|
11
|
+
Validation is performed by openapi (using OpenAPIParser). Modeling is provided by dry-struct and dry-types. Together, these provide a way for consumers to validate objects against models and to manipulate thos objects.
|
11
12
|
|
12
13
|
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
14
|
|
15
|
+
## Generate models from openapi.yml
|
14
16
|
|
15
|
-
|
17
|
+
Note that only a small subset of openapi is supported. If you are using a new openapi feature or pattern, verify that the model will be generated as expected.
|
16
18
|
|
19
|
+
### All
|
20
|
+
```
|
21
|
+
exe/generator generate
|
17
22
|
```
|
18
|
-
gem install prmd
|
19
|
-
cd docs
|
20
23
|
|
21
|
-
|
22
|
-
|
24
|
+
### Single model
|
25
|
+
```
|
26
|
+
exe/generator generate_schema DRO
|
27
|
+
```
|
23
28
|
|
24
|
-
|
25
|
-
prmd verify schema.json
|
29
|
+
## Testing
|
26
30
|
|
27
|
-
|
28
|
-
|
29
|
-
|
31
|
+
The generator is tested via its output when run against `openapi.yml`, viz., the Cocina model classes. Thus, `generate` should be run after any changes to `openapi.yml`.
|
32
|
+
|
33
|
+
Beyond what is necessary to test the generator, the Cocina model classes are not tested, i.e., they are assumed to be as specified in `openapi.yml`.
|
34
|
+
|
35
|
+
## Using this gem
|
36
|
+
|
37
|
+
If you are using this gem in an application that has an API that accepts Cocina models (e.g., SDR API, Dor-Services-App), make sure that the `openapi.yml` for the application includes the schemas that match the schemas in this `openapi.yml`.
|
30
38
|
|
31
|
-
|
39
|
+
This can be accomplished by cutting and pasting these schemas. By convention, these schemas are listed first in the `openapi.yml` of the associated projects, followed by the application-specific schemas.
|
data/cocina-models.gemspec
CHANGED
@@ -23,14 +23,19 @@ 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'
|
34
39
|
spec.add_development_dependency 'rubocop-rspec'
|
35
|
-
spec.add_development_dependency 'simplecov'
|
40
|
+
spec.add_development_dependency 'simplecov', '~> 0.17.0'
|
36
41
|
end
|
data/docs/index.html
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>API documentation</title>
|
5
|
+
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
7
|
+
|
8
|
+
|
9
|
+
<style>
|
10
|
+
body {
|
11
|
+
margin: 0;
|
12
|
+
padding: 0;
|
13
|
+
}
|
14
|
+
</style>
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
<redoc spec-url='https://raw.githubusercontent.com/sul-dlss/cocina-models/master/openapi.yml'></redoc>
|
18
|
+
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
|
19
|
+
</body>
|
20
|
+
</html>
|
data/docs/maps/DRO.json
CHANGED
@@ -84,7 +84,7 @@
|
|
84
84
|
"download": {
|
85
85
|
"description": "Download level for the DRO metadata.",
|
86
86
|
"type": "string",
|
87
|
-
"enum": ["world", "stanford", "location-based", "
|
87
|
+
"enum": ["world", "stanford", "location-based", "none"]
|
88
88
|
},
|
89
89
|
"embargo": {
|
90
90
|
"description": "Embargo metadata",
|
data/exe/generator
ADDED
@@ -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,105 @@
|
|
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 ||= (properties + all_of_properties).uniq(&:key)
|
9
|
+
end
|
10
|
+
|
11
|
+
def generate
|
12
|
+
<<~RUBY
|
13
|
+
# frozen_string_literal: true
|
14
|
+
|
15
|
+
module Cocina
|
16
|
+
module Models
|
17
|
+
class #{name} < Struct
|
18
|
+
|
19
|
+
#{types}
|
20
|
+
|
21
|
+
#{model_attributes}
|
22
|
+
|
23
|
+
#{validate}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
RUBY
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def property_class_for(properties_doc)
|
33
|
+
case properties_doc.type
|
34
|
+
when 'object'
|
35
|
+
# As a useful simplification, all objects must be references to schemas.
|
36
|
+
raise "Must use a reference for #{schema_doc.inspect}" unless properties_doc.name
|
37
|
+
|
38
|
+
SchemaRef
|
39
|
+
when 'array'
|
40
|
+
SchemaArray
|
41
|
+
else
|
42
|
+
SchemaValue
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def model_attributes
|
47
|
+
schema_properties.map(&:generate).join("\n")
|
48
|
+
end
|
49
|
+
|
50
|
+
def types
|
51
|
+
type_properties_doc = schema_doc.properties['type']
|
52
|
+
return '' if type_properties_doc.nil? || type_properties_doc.enum.nil?
|
53
|
+
|
54
|
+
types_list = type_properties_doc.enum.map { |item| "'#{item}'" }.join(",\n ")
|
55
|
+
|
56
|
+
<<~RUBY
|
57
|
+
include Checkable
|
58
|
+
|
59
|
+
TYPES = [#{types_list}].freeze
|
60
|
+
RUBY
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate
|
64
|
+
return '' unless validatable?
|
65
|
+
|
66
|
+
<<~RUBY
|
67
|
+
def self.new(attributes = default_attributes, safe = false, validate = true, &block)
|
68
|
+
Validator.validate(self, attributes.with_indifferent_access) if validate && name
|
69
|
+
super(attributes, safe, &block)
|
70
|
+
end
|
71
|
+
RUBY
|
72
|
+
end
|
73
|
+
|
74
|
+
def validatable?
|
75
|
+
!schema_doc.node_context.document.paths["/validate/#{schema_doc.name}"].nil?
|
76
|
+
end
|
77
|
+
|
78
|
+
def properties
|
79
|
+
schema_properties_for(schema_doc)
|
80
|
+
end
|
81
|
+
|
82
|
+
def all_of_properties
|
83
|
+
all_of_properties_for(schema_doc)
|
84
|
+
end
|
85
|
+
|
86
|
+
def all_of_properties_for(doc)
|
87
|
+
return [] if doc.all_of.nil?
|
88
|
+
|
89
|
+
doc.all_of.map do |all_of_schema|
|
90
|
+
# All of for this + recurse
|
91
|
+
schema_properties_for(all_of_schema) + all_of_properties_for(all_of_schema)
|
92
|
+
end.flatten
|
93
|
+
end
|
94
|
+
|
95
|
+
def schema_properties_for(doc)
|
96
|
+
doc.properties.map do |key, properties_doc|
|
97
|
+
property_class_for(properties_doc).new(properties_doc,
|
98
|
+
key: key,
|
99
|
+
required: doc.requires?(properties_doc),
|
100
|
+
parent: self)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,24 @@
|
|
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(#{array_of_type})#{omittable}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def omittable
|
12
|
+
if required
|
13
|
+
'.default([].freeze)'
|
14
|
+
else
|
15
|
+
'.meta(omittable: true)'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def array_of_type
|
20
|
+
schema_doc.items.name || "Types::#{dry_datatype(schema_doc.items)}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|