cocina-models 0.29.0 → 0.33.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.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76b2604162a420ba454bf72d767dd17cffd588c585644e7af5713a626528c354
4
- data.tar.gz: a120a35df55448923beb33b256001ea711de2a41b89a6b353e7eb8770aea160c
3
+ metadata.gz: fa8dd7285b70bcc1a81fd099ada06d39ceba9f24900076deec73dd46573df2f4
4
+ data.tar.gz: c196659cf0ce69aee69ffc3472235d68d9e4eb0503ed70aa5c79f517c8412498
5
5
  SHA512:
6
- metadata.gz: 8339f6d67fa9ce0d717383e377d68c935ec77b6eab99248a74ea0b70ef74379c449a9a004942d5495df97f3a00aedb671323cdd9a1f040961d7b85c285094c64
7
- data.tar.gz: c1e9c0dd4dbd1c079e863d914519e7c173d79eb85e164a1c2121b69b23d87b3323823c93e52fcd4227fde46338c6987bef4ec75d85b3517a62869ac5eb40a259
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
@@ -2,4 +2,11 @@
2
2
 
3
3
 
4
4
 
5
- ## Was the documentation (README, wiki) updated?
5
+ ## How was this change tested?
6
+
7
+
8
+
9
+ ## Which documentation and/or configurations were updated?
10
+
11
+
12
+
@@ -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
- [![Build Status](https://travis-ci.com/sul-dlss/cocina-models.svg?branch=master)](https://travis-ci.com/sul-dlss/cocina-models)
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
- It provides a way for consumers to validate objects against models using dry-struct and dry-types gems.
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
- ## Generate Documentation
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
- # Combine into a single schema
22
- prmd combine --meta meta.json maps/ > schema.json
24
+ ### Single model
25
+ ```
26
+ exe/generator generate_schema DRO
27
+ ```
23
28
 
24
- # Check it’s all good
25
- prmd verify schema.json
29
+ ## Testing
26
30
 
27
- # Build docs
28
- prmd doc schema.json > schema.md
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
- Then check in the resulting changes to `docs/schema.json` and `docs/schema.md`
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.
@@ -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
@@ -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>
@@ -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", "citation-only", "dark"]
87
+ "enum": ["world", "stanford", "location-based", "none"]
88
88
  },
89
89
  "embargo": {
90
90
  "description": "Embargo metadata",
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ $LOAD_PATH.unshift 'lib'
6
+
7
+ require 'cocina/models'
8
+
9
+ Cocina::Generator::Generator.start(ARGV)
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ # Module for generating Cocina models from openapi.
5
+ module Generator
6
+ end
7
+ 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,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