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