diff --git a/.rubocop.yml b/.rubocop.yml
index e35fac10..dc58e281 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,4 +1,7 @@
# This yaml describes our current checks.
+require:
+ - rubocop-performance
+
AllCops:
TargetRubyVersion: 2.5
SuggestExtensions: false
diff --git a/.yardopts b/.yardopts
index d846e0a9..67a61d2b 100644
--- a/.yardopts
+++ b/.yardopts
@@ -1,9 +1,16 @@
---no-private
---protected
---markup="markdown" lib/**/*.rb
--main README.md
---hide-tag todo
+--output-dir doc lib/**/*.rb
+--markup=markdown
+--markup-provider=redcarpet
+--private
+--protected
+--embed-mixin ClassMethods
+
+--tag nodocs --query '!@nodocs'
+--tag http:"HTTP Status"
+--tag url:"Example Endpoints"
+
-
+CHANGELOG.md
LICENSE.txt
-ChangeLog.md
-CODE_OF_CONDUCT.md
\ No newline at end of file
+CODE_OF_CONDUCT.md
diff --git a/HISTORY.md b/CHANGELOG.md
similarity index 100%
rename from HISTORY.md
rename to CHANGELOG.md
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 2874bb53..e7a87c3c 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,74 +1,127 @@
+
# Contributor Covenant Code of Conduct
## Our Pledge
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnicity, gender identity and expression, level of experience,
-nationality, personal appearance, race, religion, or sexual identity and
-orientation.
-
-## Our Standards
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual
+identity and orientation.
-Examples of behavior that contributes to creating a positive environment
-include:
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
-
-Examples of unacceptable behavior by participants include:
+## Our Standards
-* The use of sexualized language or imagery and unwelcome sexual attention or
-advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+- Focusing on what is best not just for us as individuals, but for the overall
+ community
+
+Examples of unacceptable behavior include:
+
+- The use of sexualized language or imagery, and sexual attention or advances of
+ any kind
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email address,
+ without their explicit permission
+- Other conduct which could reasonably be considered inappropriate in a
professional setting
-## Our Responsibilities
+## Enforcement Responsibilities
-Project maintainers are responsible for clarifying the standards of acceptable
-behavior and are expected to take appropriate and fair corrective action in
-response to any instances of unacceptable behavior.
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
-Project maintainers have the right and responsibility to remove, edit, or
-reject comments, commits, code, wiki edits, issues, and other contributions
-that are not aligned to this Code of Conduct, or to ban temporarily or
-permanently any contributor for other behaviors that they deem inappropriate,
-threatening, offensive, or harmful.
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
## Scope
-This Code of Conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an appointed
-representative at an online or offline event. Representation of a project may be
-further defined and clarified by project maintainers.
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official email address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported by contacting the project team at dan@ideacrew.com. All
-complaints will be reviewed and investigated and will result in a response that
-is deemed necessary and appropriate to the circumstances. The project team is
-obligated to maintain confidentiality with regard to the reporter of an incident.
-Further details of specific enforcement policies may be posted separately.
+reported to the community leaders responsible for enforcement at [info@ideacrew.com](mailto:info@ideacrew.com).
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
-Project maintainers who do not follow or enforce the Code of Conduct in good
-faith may face temporary or permanent repercussions as determined by other
-members of the project's leadership.
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series of
+actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or permanent
+ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the
+community.
## Attribution
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
-available at [https://contributor-covenant.org/version/1/4][version]
+This Code of Conduct is adapted from the
+[Contributor Covenant](https://www.contributor-covenant.org/), version 2.1,
+available at
+.
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/inclusion).
-[homepage]: https://contributor-covenant.org
-[version]: https://contributor-covenant.org/version/1/4/
+For answers to common questions about this code of conduct, see the FAQ at
+. Translations are available at
+.
diff --git a/Gemfile b/Gemfile
index 55e26d2d..4a2eceb6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,15 +1,21 @@
# frozen_string_literal: true
-source "https://rubygems.org"
+source 'https://rubygems.org'
# Specify your gem's dependencies in event_source.gemspec
gemspec
group :development, :test do
- gem "rails", '>= 6.1.4'
- gem "rspec-rails"
- gem "pry", platform: :mri, require: false
- gem "pry-byebug", platform: :mri, require: false
+ gem 'database_cleaner'
+ gem 'faker'
+ gem 'mongoid'
+ gem 'pry', platform: :mri, require: false
+ gem 'pry-byebug', platform: :mri, require: false
+ gem 'rails', '>= 6.1.4'
+ gem 'rspec-rails'
gem 'rubocop'
+ gem 'rubocop-performance', require: false
+ gem 'sinatra'
+ gem 'webmock'
gem 'yard'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index e2546959..e13c2aaf 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -5,15 +5,11 @@ PATH
addressable (>= 2.8.0)
bunny (>= 2.14)
deep_merge (~> 1.2.0)
- dry-configurable (~> 0.12)
- dry-events (~> 0.3)
- dry-inflector (~> 0.2)
- dry-initializer (~> 3.0)
- dry-monads (~> 1.3)
- dry-schema (~> 1.6)
- dry-struct (~> 1.4)
- dry-types (~> 1.5)
- dry-validation (~> 1.6)
+ dry-configurable (~> 1.0)
+ dry-events (~> 1.0)
+ dry-monads (~> 1.6)
+ dry-struct (~> 1.6)
+ dry-validation (~> 1.10)
faraday (~> 1.4.1)
faraday_middleware (~> 1.0)
logging (~> 2.3.0)
@@ -27,139 +23,160 @@ PATH
GEM
remote: https://rubygems.org/
specs:
- actioncable (6.1.4.1)
- actionpack (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ actioncable (7.1.2)
+ actionpack (= 7.1.2)
+ activesupport (= 7.1.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
- actionmailbox (6.1.4.1)
- actionpack (= 6.1.4.1)
- activejob (= 6.1.4.1)
- activerecord (= 6.1.4.1)
- activestorage (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ zeitwerk (~> 2.6)
+ actionmailbox (7.1.2)
+ actionpack (= 7.1.2)
+ activejob (= 7.1.2)
+ activerecord (= 7.1.2)
+ activestorage (= 7.1.2)
+ activesupport (= 7.1.2)
mail (>= 2.7.1)
- actionmailer (6.1.4.1)
- actionpack (= 6.1.4.1)
- actionview (= 6.1.4.1)
- activejob (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ net-imap
+ net-pop
+ net-smtp
+ actionmailer (7.1.2)
+ actionpack (= 7.1.2)
+ actionview (= 7.1.2)
+ activejob (= 7.1.2)
+ activesupport (= 7.1.2)
mail (~> 2.5, >= 2.5.4)
- rails-dom-testing (~> 2.0)
- actionpack (6.1.4.1)
- actionview (= 6.1.4.1)
- activesupport (= 6.1.4.1)
- rack (~> 2.0, >= 2.0.9)
+ net-imap
+ net-pop
+ net-smtp
+ rails-dom-testing (~> 2.2)
+ actionpack (7.1.2)
+ actionview (= 7.1.2)
+ activesupport (= 7.1.2)
+ nokogiri (>= 1.8.5)
+ racc
+ rack (>= 2.2.4)
+ rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
- rails-dom-testing (~> 2.0)
- rails-html-sanitizer (~> 1.0, >= 1.2.0)
- actiontext (6.1.4.1)
- actionpack (= 6.1.4.1)
- activerecord (= 6.1.4.1)
- activestorage (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ rails-dom-testing (~> 2.2)
+ rails-html-sanitizer (~> 1.6)
+ actiontext (7.1.2)
+ actionpack (= 7.1.2)
+ activerecord (= 7.1.2)
+ activestorage (= 7.1.2)
+ activesupport (= 7.1.2)
+ globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
- actionview (6.1.4.1)
- activesupport (= 6.1.4.1)
+ actionview (7.1.2)
+ activesupport (= 7.1.2)
builder (~> 3.1)
- erubi (~> 1.4)
- rails-dom-testing (~> 2.0)
- rails-html-sanitizer (~> 1.1, >= 1.2.0)
- activejob (6.1.4.1)
- activesupport (= 6.1.4.1)
+ erubi (~> 1.11)
+ rails-dom-testing (~> 2.2)
+ rails-html-sanitizer (~> 1.6)
+ activejob (7.1.2)
+ activesupport (= 7.1.2)
globalid (>= 0.3.6)
- activemodel (6.1.4.1)
- activesupport (= 6.1.4.1)
- activerecord (6.1.4.1)
- activemodel (= 6.1.4.1)
- activesupport (= 6.1.4.1)
- activestorage (6.1.4.1)
- actionpack (= 6.1.4.1)
- activejob (= 6.1.4.1)
- activerecord (= 6.1.4.1)
- activesupport (= 6.1.4.1)
- marcel (~> 1.0.0)
- mini_mime (>= 1.1.0)
- activesupport (6.1.4.1)
+ activemodel (7.1.2)
+ activesupport (= 7.1.2)
+ activerecord (7.1.2)
+ activemodel (= 7.1.2)
+ activesupport (= 7.1.2)
+ timeout (>= 0.4.0)
+ activestorage (7.1.2)
+ actionpack (= 7.1.2)
+ activejob (= 7.1.2)
+ activerecord (= 7.1.2)
+ activesupport (= 7.1.2)
+ marcel (~> 1.0)
+ activesupport (7.1.2)
+ base64
+ bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
+ connection_pool (>= 2.2.5)
+ drb
i18n (>= 1.6, < 2)
minitest (>= 5.1)
+ mutex_m
tzinfo (~> 2.0)
- zeitwerk (~> 2.3)
- addressable (2.8.0)
- public_suffix (>= 2.0.2, < 5.0)
+ addressable (2.8.6)
+ public_suffix (>= 2.0.2, < 6.0)
amq-protocol (2.3.2)
ast (2.4.2)
- bson (4.12.1)
+ base64 (0.2.0)
+ bigdecimal (3.1.5)
+ bson (4.15.0)
builder (3.2.4)
- bunny (2.19.0)
+ bunny (2.22.0)
amq-protocol (~> 2.3, >= 2.3.1)
sorted_set (~> 1, >= 1.0.2)
byebug (11.1.3)
coderay (1.1.3)
- concurrent-ruby (1.1.9)
+ concurrent-ruby (1.2.2)
+ connection_pool (2.4.1)
crack (0.4.5)
rexml
crass (1.0.6)
- database_cleaner (2.0.1)
- database_cleaner-active_record (~> 2.0.0)
- database_cleaner-active_record (2.0.1)
+ database_cleaner (2.0.2)
+ database_cleaner-active_record (>= 2, < 3)
+ database_cleaner-active_record (2.1.0)
activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1)
- deep_merge (1.2.1)
- diff-lcs (1.4.4)
- dry-configurable (0.12.1)
- concurrent-ruby (~> 1.0)
- dry-core (~> 0.5, >= 0.5.0)
- dry-container (0.8.0)
- concurrent-ruby (~> 1.0)
- dry-configurable (~> 0.1, >= 0.1.3)
- dry-core (0.7.1)
+ date (3.3.4)
+ deep_merge (1.2.2)
+ diff-lcs (1.5.0)
+ drb (2.2.0)
+ ruby2_keywords
+ dry-configurable (1.1.0)
+ dry-core (~> 1.0, < 2)
+ zeitwerk (~> 2.6)
+ dry-core (1.0.1)
concurrent-ruby (~> 1.0)
- dry-equalizer (0.3.0)
- dry-events (0.3.0)
+ zeitwerk (~> 2.6)
+ dry-events (1.0.1)
concurrent-ruby (~> 1.0)
- dry-core (~> 0.5, >= 0.5)
- dry-inflector (0.2.1)
- dry-initializer (3.0.4)
- dry-logic (1.2.0)
+ dry-core (~> 1.0, < 2)
+ dry-inflector (1.0.0)
+ dry-initializer (3.1.1)
+ dry-logic (1.5.0)
concurrent-ruby (~> 1.0)
- dry-core (~> 0.5, >= 0.5)
- dry-monads (1.4.0)
+ dry-core (~> 1.0, < 2)
+ zeitwerk (~> 2.6)
+ dry-monads (1.6.0)
concurrent-ruby (~> 1.0)
- dry-core (~> 0.7)
- dry-schema (1.6.2)
+ dry-core (~> 1.0, < 2)
+ zeitwerk (~> 2.6)
+ dry-schema (1.13.3)
concurrent-ruby (~> 1.0)
- dry-configurable (~> 0.8, >= 0.8.3)
- dry-core (~> 0.5, >= 0.5)
+ dry-configurable (~> 1.0, >= 1.0.1)
+ dry-core (~> 1.0, < 2)
dry-initializer (~> 3.0)
- dry-logic (~> 1.0)
- dry-types (~> 1.5)
- dry-struct (1.4.0)
- dry-core (~> 0.5, >= 0.5)
- dry-types (~> 1.5)
+ dry-logic (>= 1.4, < 2)
+ dry-types (>= 1.7, < 2)
+ zeitwerk (~> 2.6)
+ dry-struct (1.6.0)
+ dry-core (~> 1.0, < 2)
+ dry-types (>= 1.7, < 2)
ice_nine (~> 0.11)
- dry-types (1.5.1)
+ zeitwerk (~> 2.6)
+ dry-types (1.7.1)
concurrent-ruby (~> 1.0)
- dry-container (~> 0.3)
- dry-core (~> 0.5, >= 0.5)
- dry-inflector (~> 0.1, >= 0.1.2)
- dry-logic (~> 1.0, >= 1.0.2)
- dry-validation (1.6.0)
+ dry-core (~> 1.0)
+ dry-inflector (~> 1.0)
+ dry-logic (~> 1.4)
+ zeitwerk (~> 2.6)
+ dry-validation (1.10.0)
concurrent-ruby (~> 1.0)
- dry-container (~> 0.7, >= 0.7.1)
- dry-core (~> 0.4)
- dry-equalizer (~> 0.2)
+ dry-core (~> 1.0, < 2)
dry-initializer (~> 3.0)
- dry-schema (~> 1.5, >= 1.5.2)
- erubi (1.10.0)
- et-orbi (1.2.6)
+ dry-schema (>= 1.12, < 2)
+ zeitwerk (~> 2.6)
+ erubi (1.12.0)
+ et-orbi (1.2.7)
tzinfo
- ethon (0.15.0)
+ ethon (0.16.0)
ffi (>= 1.15.0)
- faker (2.18.0)
- i18n (>= 1.6, < 2)
+ faker (3.2.2)
+ i18n (>= 1.8.11, < 2)
faraday (1.4.3)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
@@ -175,99 +192,142 @@ GEM
faraday-net_http_persistent (1.2.0)
faraday_middleware (1.2.0)
faraday (~> 1.0)
- ffi (1.15.4)
- fugit (1.5.2)
- et-orbi (~> 1.1, >= 1.1.8)
+ ffi (1.16.3)
+ fugit (1.9.0)
+ et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
- globalid (0.5.2)
- activesupport (>= 5.0)
- hashdiff (1.0.1)
- i18n (1.8.10)
+ globalid (1.2.1)
+ activesupport (>= 6.1)
+ hashdiff (1.1.0)
+ i18n (1.14.1)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
+ io-console (0.7.1)
+ irb (1.11.0)
+ rdoc
+ reline (>= 0.3.8)
+ json (2.7.1)
+ language_server-protocol (3.17.0.3)
little-plugger (1.1.4)
- logging (2.3.0)
+ logging (2.3.1)
little-plugger (~> 1.1)
multi_json (~> 1.14)
- loofah (2.12.0)
+ loofah (2.22.0)
crass (~> 1.0.2)
- nokogiri (>= 1.5.9)
- mail (2.7.1)
+ nokogiri (>= 1.12.0)
+ mail (2.8.1)
mini_mime (>= 0.1.1)
+ net-imap
+ net-pop
+ net-smtp
marcel (1.0.2)
method_source (1.0.0)
- mime-types (3.3.1)
+ mime-types (3.5.1)
mime-types-data (~> 3.2015)
- mime-types-data (3.2021.0901)
- mini_mime (1.1.2)
- mini_portile2 (2.8.1)
- minitest (5.14.4)
- mongo (2.14.0)
- bson (>= 4.8.2, < 5.0.0)
- mongoid (7.3.0)
- activemodel (>= 5.1, < 6.2)
- mongo (>= 2.10.5, < 3.0.0)
- mono_logger (1.1.1)
+ mime-types-data (3.2023.1205)
+ mini_mime (1.1.5)
+ mini_portile2 (2.8.5)
+ minitest (5.20.0)
+ mongo (2.19.3)
+ bson (>= 4.14.1, < 5.0.0)
+ mongoid (8.1.4)
+ activemodel (>= 5.1, < 7.2, != 7.0.0)
+ concurrent-ruby (>= 1.0.5, < 2.0)
+ mongo (>= 2.18.0, < 3.0.0)
+ ruby2_keywords (~> 0.0.5)
+ mono_logger (1.1.2)
multi_json (1.15.0)
- multipart-post (2.1.1)
- mustermann (1.1.1)
+ multipart-post (2.3.0)
+ mustermann (3.0.0)
ruby2_keywords (~> 0.0.1)
- nio4r (2.5.8)
- oj (3.13.9)
- ox (2.14.5)
- parallel (1.20.1)
- parser (3.0.1.1)
+ mutex_m (0.2.0)
+ net-imap (0.4.8)
+ date
+ net-protocol
+ net-pop (0.1.2)
+ net-protocol
+ net-protocol (0.2.2)
+ timeout
+ net-smtp (0.4.0)
+ net-protocol
+ nio4r (2.7.0)
+ nokogiri (1.15.5)
+ mini_portile2 (~> 2.8.2)
+ racc (~> 1.4)
+ oj (3.16.3)
+ bigdecimal (>= 3.0)
+ ox (2.14.17)
+ parallel (1.24.0)
+ parser (3.2.2.4)
ast (~> 2.4.1)
- pry (0.14.1)
+ racc
+ pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
- pry-byebug (3.8.0)
+ pry-byebug (3.10.1)
byebug (~> 11.0)
- pry (~> 0.10)
- public_suffix (4.0.6)
- queue-bus (0.12.0)
+ pry (>= 0.13, < 0.15)
+ psych (5.1.2)
+ stringio
+ public_suffix (5.0.4)
+ queue-bus (0.13.2)
multi_json
redis
raabro (1.4.0)
- racc (1.6.2)
- rack (2.2.3)
- rack-protection (2.1.0)
- rack
- rack-test (1.1.0)
- rack (>= 1.0, < 3)
- rails (6.1.4.1)
- actioncable (= 6.1.4.1)
- actionmailbox (= 6.1.4.1)
- actionmailer (= 6.1.4.1)
- actionpack (= 6.1.4.1)
- actiontext (= 6.1.4.1)
- actionview (= 6.1.4.1)
- activejob (= 6.1.4.1)
- activemodel (= 6.1.4.1)
- activerecord (= 6.1.4.1)
- activestorage (= 6.1.4.1)
- activesupport (= 6.1.4.1)
+ racc (1.7.3)
+ rack (2.2.8)
+ rack-protection (3.1.0)
+ rack (~> 2.2, >= 2.2.4)
+ rack-session (1.0.2)
+ rack (< 3)
+ rack-test (2.1.0)
+ rack (>= 1.3)
+ rackup (1.0.0)
+ rack (< 3)
+ webrick
+ rails (7.1.2)
+ actioncable (= 7.1.2)
+ actionmailbox (= 7.1.2)
+ actionmailer (= 7.1.2)
+ actionpack (= 7.1.2)
+ actiontext (= 7.1.2)
+ actionview (= 7.1.2)
+ activejob (= 7.1.2)
+ activemodel (= 7.1.2)
+ activerecord (= 7.1.2)
+ activestorage (= 7.1.2)
+ activesupport (= 7.1.2)
bundler (>= 1.15.0)
- railties (= 6.1.4.1)
- sprockets-rails (>= 2.0.0)
- rails-dom-testing (2.0.3)
- activesupport (>= 4.2.0)
+ railties (= 7.1.2)
+ rails-dom-testing (2.2.0)
+ activesupport (>= 5.0.0)
+ minitest
nokogiri (>= 1.6)
- rails-html-sanitizer (1.4.2)
- loofah (~> 2.3)
- railties (6.1.4.1)
- actionpack (= 6.1.4.1)
- activesupport (= 6.1.4.1)
- method_source
- rake (>= 0.13)
- thor (~> 1.0)
- rainbow (3.0.0)
- rake (13.0.6)
+ rails-html-sanitizer (1.6.0)
+ loofah (~> 2.21)
+ nokogiri (~> 1.14)
+ railties (7.1.2)
+ actionpack (= 7.1.2)
+ activesupport (= 7.1.2)
+ irb
+ rackup (>= 1.0.0)
+ rake (>= 12.2)
+ thor (~> 1.0, >= 1.2.2)
+ zeitwerk (~> 2.6)
+ rainbow (3.1.1)
+ rake (13.1.0)
rbtree (0.4.6)
- redis (4.5.1)
- redis-namespace (1.8.1)
- redis (>= 3.0.4)
- regexp_parser (2.1.1)
+ rdoc (6.6.2)
+ psych (>= 4.0.0)
+ redis (5.0.8)
+ redis-client (>= 0.17.0)
+ redis-client (0.19.1)
+ connection_pool
+ redis-namespace (1.11.0)
+ redis (>= 4)
+ regexp_parser (2.8.3)
+ reline (0.4.1)
+ io-console (~> 0.5)
resque (1.27.4)
mono_logger (~> 1.0)
multi_json (~> 1.0)
@@ -279,81 +339,82 @@ GEM
resque (>= 1.10.0, < 2.0)
resque-retry
resque-scheduler (>= 2.0.1)
- resque-retry (1.7.6)
+ resque-retry (1.8.1)
resque (>= 1.25, < 3.0)
- resque-scheduler (~> 4.0)
- resque-scheduler (4.5.0)
+ resque-scheduler (>= 4.0, < 6.0)
+ resque-scheduler (4.10.2)
mono_logger (~> 1.0)
redis (>= 3.3)
resque (>= 1.27)
- rufus-scheduler (~> 3.2, < 3.7)
- rexml (3.2.5)
- rspec-core (3.10.1)
- rspec-support (~> 3.10.0)
- rspec-expectations (3.10.1)
+ rufus-scheduler (~> 3.2, != 3.3)
+ rexml (3.2.6)
+ rspec-core (3.12.2)
+ rspec-support (~> 3.12.0)
+ rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.10.0)
- rspec-mocks (3.10.2)
+ rspec-support (~> 3.12.0)
+ rspec-mocks (3.12.6)
diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.10.0)
- rspec-rails (5.0.1)
- actionpack (>= 5.2)
- activesupport (>= 5.2)
- railties (>= 5.2)
- rspec-core (~> 3.10)
- rspec-expectations (~> 3.10)
- rspec-mocks (~> 3.10)
- rspec-support (~> 3.10)
- rspec-support (3.10.2)
- rubocop (1.10.0)
+ rspec-support (~> 3.12.0)
+ rspec-rails (6.1.0)
+ actionpack (>= 6.1)
+ activesupport (>= 6.1)
+ railties (>= 6.1)
+ rspec-core (~> 3.12)
+ rspec-expectations (~> 3.12)
+ rspec-mocks (~> 3.12)
+ rspec-support (~> 3.12)
+ rspec-support (3.12.1)
+ rubocop (1.59.0)
+ json (~> 2.3)
+ language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
- parser (>= 3.0.0.0)
+ parser (>= 3.2.2.4)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
- rexml
- rubocop-ast (>= 1.2.0, < 2.0)
+ rexml (>= 3.2.5, < 4.0)
+ rubocop-ast (>= 1.30.0, < 2.0)
ruby-progressbar (~> 1.7)
- unicode-display_width (>= 1.4.0, < 3.0)
- rubocop-ast (1.7.0)
- parser (>= 3.0.1.1)
- ruby-progressbar (1.11.0)
- ruby2_keywords (0.0.4)
- rufus-scheduler (3.6.0)
+ unicode-display_width (>= 2.4.0, < 3.0)
+ rubocop-ast (1.30.0)
+ parser (>= 3.2.1.0)
+ rubocop-performance (1.20.0)
+ rubocop (>= 1.48.1, < 2.0)
+ rubocop-ast (>= 1.30.0, < 2.0)
+ ruby-progressbar (1.13.0)
+ ruby2_keywords (0.0.5)
+ rufus-scheduler (3.9.1)
fugit (~> 1.1, >= 1.1.6)
- set (1.0.2)
- sinatra (2.1.0)
- mustermann (~> 1.0)
- rack (~> 2.2)
- rack-protection (= 2.1.0)
+ set (1.0.4)
+ sinatra (3.1.0)
+ mustermann (~> 3.0)
+ rack (~> 2.2, >= 2.2.4)
+ rack-protection (= 3.1.0)
tilt (~> 2.0)
sorted_set (1.0.3)
rbtree
set (~> 1.0)
- sprockets (4.0.2)
- concurrent-ruby (~> 1.0)
- rack (> 1, < 3)
- sprockets-rails (3.2.2)
- actionpack (>= 4.0)
- activesupport (>= 4.0)
- sprockets (>= 3.0.0)
- thor (1.1.0)
- tilt (2.0.10)
- typhoeus (1.4.0)
+ stringio (3.1.0)
+ thor (1.3.0)
+ tilt (2.3.0)
+ timeout (0.4.1)
+ typhoeus (1.4.1)
ethon (>= 0.9.0)
- tzinfo (2.0.4)
+ tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
- unicode-display_width (2.0.0)
+ unicode-display_width (2.5.0)
vegas (0.1.11)
rack (>= 1.0.0)
- webmock (3.13.0)
- addressable (>= 2.3.6)
+ webmock (3.19.1)
+ addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
- websocket-driver (0.7.5)
+ webrick (1.8.1)
+ websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
- yard (0.9.26)
- zeitwerk (2.5.1)
+ yard (0.9.34)
+ zeitwerk (2.6.12)
PLATFORMS
ruby
@@ -368,6 +429,7 @@ DEPENDENCIES
rails (>= 6.1.4)
rspec-rails
rubocop
+ rubocop-performance
sinatra
webmock
yard
diff --git a/LICENSE.txt b/LICENSE.txt
index 27b1a6fb..52555bf6 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2019-2021 IdeaCrew, Inc
+Copyright (c) 2019-2025 IdeaCrew, Inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/event_source.gemspec b/event_source.gemspec
index 51b829e2..c361dc3c 100644
--- a/event_source.gemspec
+++ b/event_source.gemspec
@@ -10,9 +10,9 @@ Gem::Specification.new do |spec|
spec.version = EventSource::VERSION
spec.authors = ['Dan Thomas']
spec.email = ['info@ideacrew.com']
+ spec.metadata['rubygems_mfa_required'] = 'true'
- spec.summary =
- 'Record changes to application state by storing updates as a sequence of events'
+ spec.summary = 'Record changes to application state by storing updates as a sequence of events'
spec.description =
"This service uses Mogoid/MongoDB to create an event object to record a state change and
then processes it to update values in the underlying model. It's an implementation of
@@ -26,9 +26,7 @@ Gem::Specification.new do |spec|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files =
Dir.chdir(File.expand_path('..', __FILE__)) do
- `git ls-files -z`.split("\x0").reject do |f|
- f.match(%r{^(test|spec|features)/})
- end
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end
spec.bindir = 'exe'
@@ -38,15 +36,11 @@ Gem::Specification.new do |spec|
spec.add_dependency 'addressable', '>= 2.8.0'
spec.add_dependency 'bunny', '>= 2.14'
spec.add_dependency 'deep_merge', '~> 1.2.0'
- spec.add_dependency 'dry-configurable', '~> 0.12'
- spec.add_dependency 'dry-events', '~> 0.3'
- spec.add_dependency 'dry-inflector', '~> 0.2'
- spec.add_dependency 'dry-initializer', '~> 3.0'
- spec.add_dependency 'dry-monads', '~> 1.3'
- spec.add_dependency 'dry-struct', '~> 1.4'
- spec.add_dependency 'dry-types', '~> 1.5'
- spec.add_dependency 'dry-validation', '~> 1.6'
- spec.add_dependency 'dry-schema', '~> 1.6'
+ spec.add_dependency 'dry-configurable', '~> 1.0'
+ spec.add_dependency 'dry-events', '~> 1.0'
+ spec.add_dependency 'dry-monads', '~> 1.6'
+ spec.add_dependency 'dry-struct', '~> 1.6'
+ spec.add_dependency 'dry-validation', '~> 1.10'
spec.add_dependency 'faraday', '~> 1.4.1'
spec.add_dependency 'faraday_middleware', '~> 1.0'
spec.add_dependency 'logging', '~> 2.3.0'
@@ -56,11 +50,4 @@ Gem::Specification.new do |spec|
spec.add_dependency 'ox', '~> 2.14'
spec.add_dependency 'resque-bus', '~> 0.7.0'
spec.add_dependency 'typhoeus', '~> 1.4.0'
-
- # TODO: Change to development dependency
- spec.add_development_dependency 'database_cleaner'
- spec.add_development_dependency 'faker'
- spec.add_development_dependency 'mongoid'
- spec.add_development_dependency 'webmock'
- spec.add_development_dependency 'sinatra'
end
diff --git a/lib/event_source.rb b/lib/event_source.rb
index 67615316..d2f0d06a 100644
--- a/lib/event_source.rb
+++ b/lib/event_source.rb
@@ -2,7 +2,6 @@
require 'forwardable'
require 'date'
-require 'dry/types/type'
require 'dry/monads'
require 'dry/monads/do'
require 'dry/monads/result'
@@ -76,13 +75,11 @@ def config
# Call this method on fork of a rails app you are working in.
# It cleans up your connections and channels and avoids strange
# behaviour.
- def reconnect_publishers!; end
+ def reconnect_publishers!
+ end
def build_async_api_resource(resource)
- EventSource::AsyncApi::Operations::AsyncApiConf::Create
- .new
- .call(resource)
- .success
+ EventSource::AsyncApi::Operations::AsyncApiConf::Create.new.call(resource).success
end
end
diff --git a/lib/event_source/async_api/async_api.rb b/lib/event_source/async_api/async_api.rb
index ede976c9..823b80f6 100644
--- a/lib/event_source/async_api/async_api.rb
+++ b/lib/event_source/async_api/async_api.rb
@@ -18,6 +18,7 @@ module AsyncApi
require_relative 'error'
require_relative 'types'
require_relative 'external_documentation'
+ require_relative 'schema_object'
require_relative 'schema'
require_relative 'parameter'
require_relative 'tag'
@@ -25,6 +26,7 @@ module AsyncApi
require_relative 'contact'
require_relative 'info'
require_relative 'message_trait'
+ require_relative 'message_binding'
require_relative 'message'
require_relative 'operation_trait'
require_relative 'operation'
diff --git a/lib/event_source/async_api/contracts/message_binding_contract.rb b/lib/event_source/async_api/contracts/message_binding_contract.rb
new file mode 100644
index 00000000..1e1f75ad
--- /dev/null
+++ b/lib/event_source/async_api/contracts/message_binding_contract.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module EventSource
+ module AsyncApi
+ module Contracts
+ # Schema and validation rules for {EventSource::AsyncApi::MessageBinding} domain object
+ class MessageBindingContract < Contract
+ # @!method call(opts)
+ # @param [Hash] opts the parameters to validate using this contract
+ # @option opts [String] :content_encoding optional
+ # @option opts [String] :message_type optional
+ # @option opts [String] :binding_version optional
+ # @return [Dry::Monads::Result::Success] if params pass validation
+ # @return [Dry::Monads::Result::Failure] if params fail validation
+ params do
+ optional(:content_encoding).maybe(:string)
+ optional(:message_type).maybe(:string)
+ optional(:binding_version).maybe(:string)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/event_source/async_api/contracts/message_contract.rb b/lib/event_source/async_api/contracts/message_contract.rb
index e09e9a63..9b991362 100644
--- a/lib/event_source/async_api/contracts/message_contract.rb
+++ b/lib/event_source/async_api/contracts/message_contract.rb
@@ -1,48 +1,67 @@
# frozen_string_literal: true
+require 'event_source/async_api/contracts/schema_contract'
+require 'event_source/async_api/contracts/message_binding_contract'
+require 'event_source/async_api/contracts/message_trait_contract'
+
+
module EventSource
module AsyncApi
module Contracts
# Schema and validation rules for {EventSource::AsyncApi::Message}
- class MessageContract < Contract
+ class MessageContract < Dry::Validation::Contract
# @!method call(opts)
# @param [Hash] opts the parameters to validate using this contract
- # @option opts [Hash] :headers optional
- # @option opts [Mixed] :payload optional
- # @option opts [String] :schema_format optional
+ # @option opts [EventSource::AsyncApi::Contracts::SchemaContract] :headers optional
+ # @option opts [EventSource::AsyncApi::Contracts::SchemaContract] :payload optional
# @option opts [String] :content_type optional
# @option opts [String] :name optional
# @option opts [String] :title optional
# @option opts [String] :summary optional
# @option opts [String] :description optional
- # @option opts [Array] :tags optional
+ # @option opts [Array] :tags optional
# @option opts [ExternalDocumentation] :external_docs optional
- # @option opts [Hash] :bindings optional
+ # @option opts [EventSource::AsyncApi::Contracts::MessageBindingContract] :bindings optional
# @option opts [Array] :examples optional
# @option opts [Array] :traits optional
- # @return [Dry::Monads::Result::Success, Dry::Monads::Result::Failure]
+ # @return [Dry::Monads::Result::Success] if params pass validation
+ # @return [Dry::Monads::Result::Failure] if params fail validation
params do
- optional(:headers).maybe(Types::HashOrNil)
- optional(:payload).maybe(:any)
- optional(:schema_format).maybe(:string)
- optional(:contentType).maybe(:string)
+ optional(:headers).maybe(EventSource::AsyncApi::Contracts::SchemaContract.params)
+ optional(:payload).maybe(EventSource::AsyncApi::Contracts::SchemaContract.params)
+ optional(:correlation_id).hash do
+ optional(:description).maybe(:string)
+ required(:location).filled(:string)
+ end
+ optional(:content_type).maybe(:string)
optional(:name).maybe(:string)
optional(:title).maybe(:string)
optional(:summary).maybe(:string)
optional(:description).maybe(:string)
- optional(:tags).array(Types::HashOrNil)
+ optional(:tags).array(EventSource::AsyncApi::Contracts::TagContract.params)
optional(:external_docs).array(Types::HashOrNil)
- optional(:bindings).maybe(Types::HashOrNil)
+ optional(:bindings).maybe(EventSource::AsyncApi::Contracts::MessageBindingContract.params)
optional(:examples).maybe(Types::HashOrNil)
- optional(:traits).array(Types::HashOrNil)
+ optional(:traits).array(EventSource::AsyncApi::Contracts::MessageTraitContract.params)
+ # Coerce empty attributes with default values
before(:value_coercer) do |result|
- if result.to_h.key?(:external_docs) &&
- result.to_h[:external_docs].nil?
+ if result.to_h.key?(:external_docs).nil? || result.to_h[:external_docs].nil?
+ result.to_h.merge!(default_external_docs)
+ end
- result.to_h.merge!({ external_docs: Array.new })
+ if (result.to_h.key?(:correlation_id).nil? || result.to_h[:correlation_id].nil?)
+ result.to_h.merge!(default_correlation_id)
end
end
+
+ def default_external_docs
+ { external_docs: Array.new }
+ end
+
+ def default_correlation_id
+ { description: 'Default Correlation ID', location: '$message.header#/correlationId' }
+ end
end
end
end
diff --git a/lib/event_source/async_api/contracts/message_trait_contract.rb b/lib/event_source/async_api/contracts/message_trait_contract.rb
new file mode 100644
index 00000000..41748168
--- /dev/null
+++ b/lib/event_source/async_api/contracts/message_trait_contract.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'event_source/async_api/contracts/schema_contract'
+require 'event_source/async_api/contracts/message_binding_contract'
+
+module EventSource
+ module AsyncApi
+ module Contracts
+ # Schema and validation rules for {EventSource::AsyncApi::Message}
+ class MessageTraitContract < Dry::Validation::Contract
+ # @!method call(opts)
+ # @param [Hash] opts the parameters to validate using this contract
+ # @option opts [EventSource::AsyncApi::Contracts::SchemaContract] :headers optional
+ # @option opts [String] :content_type optional
+ # @option opts [String] :name optional
+ # @option opts [String] :title optional
+ # @option opts [String] :summary optional
+ # @option opts [String] :description optional
+ # @option opts [Array] :tags optional
+ # @option opts [ExternalDocumentation] :external_docs optional
+ # @option opts [EventSource::AsyncApi::Contracts::MessageBindingContract] :bindings optional
+ # @option opts [Array] :examples optional
+ # @return [Dry::Monads::Result::Success] if params pass validation
+ # @return [Dry::Monads::Result::Failure] if params fail validation
+ params do
+ optional(:headers).maybe(EventSource::AsyncApi::Contracts::SchemaContract.params)
+ optional(:correlation_id).hash do
+ optional(:description).maybe(:string)
+ required(:location).filled(:string)
+ end
+ optional(:content_type).maybe(:string)
+ optional(:name).maybe(:string)
+ optional(:title).maybe(:string)
+ optional(:summary).maybe(:string)
+ optional(:description).maybe(:string)
+ optional(:tags).array(EventSource::AsyncApi::Contracts::TagContract.params)
+ optional(:external_docs).array(Types::HashOrNil)
+ optional(:bindings).maybe(EventSource::AsyncApi::Contracts::MessageBindingContract.params)
+ optional(:examples).maybe(Types::HashOrNil)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/event_source/async_api/contracts/schema_contract.rb b/lib/event_source/async_api/contracts/schema_contract.rb
new file mode 100644
index 00000000..ac160364
--- /dev/null
+++ b/lib/event_source/async_api/contracts/schema_contract.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'event_source/async_api/contracts/schema_object_contract'
+
+module EventSource
+ module AsyncApi
+ module Contracts
+ # Schema and validation rules for {EventSource::AsyncApi::Schema} domain object
+ class SchemaContract < Contract
+ # @!method call(opts)
+ # @param [Hash] opts the parameters to validate using this contract
+ # @option opts [String] :schema_format optional
+ # @option opts [EventSource::AcaEntities::SchemaObjectContract] :schema optional
+ # @return [Dry::Monads::Result::Success] if params pass validation
+ # @return [Dry::Monads::Result::Failure] if params fail validation
+ params do
+ optional(:schema_format).maybe(:string)
+ optional(:schema).maybe(EventSource::AsyncApi::Contracts::SchemaObjectContract.params)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/event_source/async_api/contracts/schema_object_contract.rb b/lib/event_source/async_api/contracts/schema_object_contract.rb
new file mode 100644
index 00000000..d9519531
--- /dev/null
+++ b/lib/event_source/async_api/contracts/schema_object_contract.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module EventSource
+ module AsyncApi
+ module Contracts
+ # Schema and validation rules for {EventSource::AsyncApi::SchemaObject} domain object
+ class SchemaObjectContract < Contract
+ # @!method call(opts)
+ # @param [Hash] opts the parameters to validate using this contract
+ # @option opts [String] :type required
+ # @option opts [String] :required optional
+ # @option opts [Hash] :properties
+ # @return [Dry::Monads::Result::Success] if params pass validation
+ # @return [Dry::Monads::Result::Failure] if params fail validation
+ params do
+ required(:type).filled(:string)
+ optional(:required).array(:string)
+ optional(:properties).maybe(:hash)
+ end
+
+ rule(:required).each do
+ property_keys = result.to_h[:properties].keys
+ key.failure("#{value}: not defined in properties") unless property_keys.include? value.to_sym
+ end
+ end
+ end
+ end
+end
diff --git a/lib/event_source/async_api/message.rb b/lib/event_source/async_api/message.rb
index dc28c383..04aa9919 100644
--- a/lib/event_source/async_api/message.rb
+++ b/lib/event_source/async_api/message.rb
@@ -15,98 +15,86 @@ module AsyncApi
#
# Describes a message received on a given channel and operation
class Message < Dry::Struct
- transform_keys(&:to_sym)
-
# @!attribute [r] headers
- # Schema definition of the application headers. Schema must be of type "object".
- # It must not define the protocol headers.
+ # Schema definition of the application headers (it MUST NOT define the protocol headers).
# @return [Schema]
- attribute :headers, Schema.meta(omittable: true)
+ attribute? :headers, EventSource::AsyncApi::Schema.meta(omittable: true)
# @!attribute [r] payload
# Definition of the message payload. It can be of any type but defaults to Schema object
# @return [Types::Any]
- attribute :payload, Types::Any.meta(omittable: true)
+ attribute? :payload, Types::Hash.meta(omittable: true)
# @!attribute [r] correlation_id
# Definition of the correlation ID used for message tracing or matching
# @return [String]
- attribute :correlation_id do
+ attribute? :correlation_id do
# @!attribute [r] description
# An optional description of the identifier.
# CommonMark syntax can be used for rich text representation
# @return [String]
- attribute :description, Types::String.meta(omittable: true)
+ attribute? :description, Types::String.meta(omittable: true)
# @!attribute [r] location
# Required. A runtime expression that specifies the location of the correlation ID
# @return [String]
- attribute :location, Types::String.meta(omittable: true)
+ attribute :location, Types::String.meta(omittable: false)
end.meta(omittable: true)
- # @!attribute [r] schema_format
- # A string containing the name of the schema format used to define the message payload.
- # If omitted, implementations should parse the payload as a Schema object. Check out the
- # supported schema formats table for more information. Custom values are allowed but
- # their implementation is OPTIONAL. A custom value must not refer to one of the schema formats
- # listed in the table.
- # @return [String]
- attribute :schema_format, Types::String.meta(omittable: true)
-
# @!attribute [r] content_type
# The content type to use when encoding/decoding a message's payload. The value must be a
# specific media type (e.g. application/json). When omitted, the value must be the one specified
# on the default_content_type field
# @return [String]
- attribute :contentType, Types::String.meta(omittable: true)
+ attribute? :content_type, Types::String.meta(omittable: true)
# @!attribute [r] name
# A machine-friendly name for the message
# @return [String]
- attribute :name, Types::String.meta(omittable: true)
+ attribute? :name, Types::String.meta(omittable: true)
# @!attribute [r] title
# A human-friendly title for the message
# @return [String]
- attribute :title, Types::String.meta(omittable: true)
+ attribute? :title, Types::String.meta(omittable: true)
# @!attribute [r] summary
# A short summary of what the message is about
# @return [String]
- attribute :summary, Types::String.meta(omittable: true)
+ attribute? :summary, Types::String.meta(omittable: true)
# @!attribute [r] description
# A verbose explanation of the message. CommonMark syntax can be used for rich text representation
# @return [String]
- attribute :description, Types::String.meta(omittable: true)
+ attribute? :description, Types::String.meta(omittable: true)
# @!attribute [r] tags
# A list of tags for API documentation control. Tags can be used for logical grouping of messages
# @return [Array]
- attribute :tags, Types::Array.of(Tag).meta(omittable: true)
+ attribute? :tags, Types::Array.of(EventSource::AsyncApi::Tag).meta(omittable: true)
# @!attribute [r] description
# Additional external documentation for this message
# @return [ExternalDocumentation]
- attribute :external_docs, ExternalDocumentation.meta(omittable: true)
+ attribute? :external_docs, Types::Array.of(EventSource::AsyncApi::ExternalDocumentation).meta(omittable: true)
# @!attribute [r] bindings
# Map where the keys describe the name of the protocol and the values describe protocol-specific
# definitions for the message
# @return [Hash]
- attribute :bindings, Types::Hash.meta(omittable: true)
+ attribute? :bindings, Types::Hash.meta(omittable: true)
# @!attribute [r] examples
# An array with examples of valid message objects
# @return [Hash]
- attribute :examples, Types::Array.of(Hash).meta(omittable: true)
+ attribute? :examples, Types::Array.of(Hash).meta(omittable: true)
# @!attribute [r] traits
# A list of traits to apply to the message object. Traits must be merged into the message object
# using the JSON Merge Patch algorithm in the same order they are defined here. The resulting
# object must be a valid Message Object
# @return [Array]
- attribute :traits, Types::Array.of(MessageTrait).meta(omittable: true)
+ attribute? :traits, Types::Array.of(EventSource::AsyncApi::MessageTrait).meta(omittable: true)
end
end
end
diff --git a/lib/event_source/async_api/message_binding.rb b/lib/event_source/async_api/message_binding.rb
new file mode 100644
index 00000000..6e9ec8a7
--- /dev/null
+++ b/lib/event_source/async_api/message_binding.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module EventSource
+ module AsyncApi
+ # A map where the keys describe the name of the protocol and the values describe protocol-specific definitions
+ # for the message
+ class MessageBinding < Dry::Struct
+ # @!attribute [r] content_encoding
+ # Returns a MIME encoding for the message content
+ # @return [String]
+ attribute? :content_encoding, Types::String.meta(omittable: true)
+
+ # @!attribute [r] message_type
+ # Returns the application assigned message type
+ # @return [Types::String]
+ attribute? :message_type, Types::String.meta(omittable: true)
+
+ # @!attribute [r] binding_version
+ # Returns the version of this binding. If omitted, "latest" MUST be assumed
+ # @return [Types::String]
+ attribute? :binding_version, Types::String.meta(omittable: true)
+ end
+ end
+end
diff --git a/lib/event_source/async_api/message_trait.rb b/lib/event_source/async_api/message_trait.rb
index fc0541dc..f26470db 100644
--- a/lib/event_source/async_api/message_trait.rb
+++ b/lib/event_source/async_api/message_trait.rb
@@ -5,89 +5,74 @@ module AsyncApi
# Describes a trait that may be applied to a {Message} object. This object may contain any property
# from the Message object, except payload and traits
class MessageTrait < Dry::Struct
- transform_keys(&:to_sym)
-
# @!attribute [r] headers
- # Schema definition of the application headers. Schema must be of type "object".
- # It must not define the protocol headers.
- # @return [EventSource::AsyncApi::Schema]
- attribute :headers, EventSource::AsyncApi::Schema
+ # Schema definition of the application headers (it MUST NOT define the protocol headers).
+ # @return [Schema]
+ attribute? :headers, EventSource::AsyncApi::Schema.meta(omittable: true)
# @!attribute [r] correlation_id
# Definition of the correlation ID used for message tracing or matching
# @return [String]
- attribute :correlation_id do
+ attribute? :correlation_id do
# @!attribute [r] description
# An optional description of the identifier.
# CommonMark syntax can be used for rich text representation
# @return [String]
- attribute :description, Types::String
+ attribute? :description, Types::String.meta(omittable: true)
# @!attribute [r] location
# Required. A runtime expression that specifies the location of the correlation ID
# @return [String]
- attribute :location, Types::String
- end
-
- # @!attribute [r] schema_format
- # A string containing the name of the schema format used to define the message payload.
- # If omitted, implementations should parse the payload as a Schema object. Check out the
- # supported schema formats table for more information. Custom values are allowed but
- # their implementation is OPTIONAL. A custom value must not refer to one of the schema formats
- # listed in the table.
- # @return [String]
- attribute :schema_format, Types::String
+ attribute :location, Types::String.meta(omittable: false)
+ end.meta(omittable: true)
# @!attribute [r] content_type
# The content type to use when encoding/decoding a message's payload. The value must be a
# specific media type (e.g. application/json). When omitted, the value must be the one specified
# on the default_content_type field
# @return [String]
- attribute :content_type, Types::String
+ attribute? :content_type, Types::String.meta(omittable: true)
# @!attribute [r] name
# A machine-friendly name for the message
# @return [String]
- attribute :name, Types::String
+ attribute? :name, Types::String.meta(omittable: true)
# @!attribute [r] title
# A human-friendly title for the message
# @return [String]
- attribute :title, Types::String
+ attribute? :title, Types::String.meta(omittable: true)
# @!attribute [r] summary
# A short summary of what the message is about
# @return [String]
- attribute :summary, Types::String
+ attribute? :summary, Types::String.meta(omittable: true)
# @!attribute [r] description
# A verbose explanation of the message. CommonMark syntax can be used for rich text representation
# @return [String]
- attribute :description, Types::String
+ attribute? :description, Types::String.meta(omittable: true)
# @!attribute [r] tags
# A list of tags for API documentation control. Tags can be used for logical grouping of messages
- # @return [Array]
- attribute :tags,
- Types::Array
- .of(EventSource::AsyncApi::Tag)
- .meta(omittable: true)
+ # @return [Array]
+ attribute? :tags, Types::Array.of(EventSource::AsyncApi::Tag).meta(omittable: true)
# @!attribute [r] description
# Additional external documentation for this message
- # @return [EventSource::AsyncApi::ExternalDocumentation]
- attribute :external_docs, EventSource::AsyncApi::ExternalDocumentation
+ # @return [ExternalDocumentation]
+ attribute? :external_docs, Types::Array.of(EventSource::AsyncApi::ExternalDocumentation).meta(omittable: true)
# @!attribute [r] bindings
# Map where the keys describe the name of the protocol and the values describe protocol-specific
# definitions for the message
# @return [Hash]
- attribute :bindings, Types::Hash
+ attribute? :bindings, Types::Hash.meta(omittable: true)
# @!attribute [r] examples
# An array with examples of valid message objects
# @return [Hash]
- attribute :examples, Types::Array.of(Hash)
+ attribute? :examples, Types::Array.of(Hash).meta(omittable: true)
end
end
end
diff --git a/lib/event_source/async_api/schema.rb b/lib/event_source/async_api/schema.rb
index 20f7fd0f..fd24ac34 100644
--- a/lib/event_source/async_api/schema.rb
+++ b/lib/event_source/async_api/schema.rb
@@ -1,11 +1,22 @@
# frozen_string_literal: true
+require 'event_source/async_api/schema_object'
+
module EventSource
module AsyncApi
- # Allows the definition of input and output data types. These types can be objects,
- # but also primitives and arrays. This object is a superset of the
- # JSON Schema Specification Draft 07
+ # A definition of input and output data types. These types can be objects, but also primitives and arrays.
class Schema < Dry::Struct
+ # @!attribute [r] schema_format
+ # Returns a string containing the name of the schema format used to define the attributes as defined in
+ # [AsyncApi Schema Format](https://www.asyncapi.com/docs/reference/specification/v3.0.0#multiFormatSchemaObject).
+ # If omitted, implementations should parse the payload as a Schema object.
+ # @return [String]
+ attribute? :schema_format, Types::String.meta(omittable: true)
+
+ # @!attribute [r] schema
+ # Schema in form of [AsyncApi Schema Object](https://www.asyncapi.com/docs/reference/specification/v3.0.0#schemaObject)
+ # @return [EventSource::AsyncApi::SchemaObject]
+ attribute? :schema, EventSource::AsyncApi::SchemaObject.meta(omittable: true)
end
end
end
diff --git a/lib/event_source/async_api/schema_object.rb b/lib/event_source/async_api/schema_object.rb
new file mode 100644
index 00000000..44ebebf2
--- /dev/null
+++ b/lib/event_source/async_api/schema_object.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module EventSource
+ module AsyncApi
+ # A definition of input and output data types compliant with
+ # [AsyncApi Schema Object](https://www.asyncapi.com/docs/reference/specification/v3.0.0#schemaObject)
+ # @example
+ #
+ # {
+ # type: 'object',
+ # required: ['name'],
+ # properties: {
+ # name: {
+ # type: 'string'
+ # },
+ # address: {
+ # '$ref': '#/components/schemas/Address'
+ # },
+ # age: {
+ # type: 'integer',
+ # format: 'int32',
+ # minimum: 0
+ # }
+ # }
+ # }
+ class SchemaObject < Dry::Struct
+ # @!attribute [r] type
+ # Returns the AsyncApi defined schema type
+ # @return [Types::String]
+ attribute :type, Types::String.meta(omittable: false)
+
+ # @!attribute [r] required
+ # Returns a list of attribute keys that must be included in
+ # @return [Array]
+ attribute? :required, Types::Array.of(Types::String).meta(omittable: true)
+
+ # @!attribute [r] properties
+ # Returns attribute definitions in JSON schema form
+ # @return [Types::Hash]
+ attribute? :properties, Types::Hash.meta(omittable: true)
+ end
+ end
+end
diff --git a/lib/event_source/async_api/types.rb b/lib/event_source/async_api/types.rb
index e1a97523..622fcf9a 100644
--- a/lib/event_source/async_api/types.rb
+++ b/lib/event_source/async_api/types.rb
@@ -18,7 +18,7 @@ module Types
# end
UriKind =
Types.Constructor(EventSource::Uris::Uri) do |val|
- EventSource::Uris::Uri.new(uri: val)
+ EventSource::Uris::Uri.new(uri: val).to_s
end
# UriKind = Types.Constructor(::URI, &:parse)
@@ -27,10 +27,7 @@ module Types
# TypeContainer = Dry::Schema::TypeContainer.new
# TypeContainer.register('params.uri', UriKind)
- Email =
- Coercible::String.constrained(
- format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
- )
+ Email = Coercible::String.constrained(format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i)
Emails = Array.of(Email)
HashOrNil = Types::Hash | Types::Nil
@@ -66,24 +63,13 @@ module Types
:message_bindings
)
Vhost = Types::Coercible::String.default('/')
- ChannelTypeKind =
- Types::Coercible::Symbol
- .default(:routing_key)
- .enum(:routing_key, :queue)
- ExchangeTypeKind =
- Types::Coercible::Symbol.enum(
- :topic,
- :fanout,
- :default,
- :direct,
- :headers
- )
+ ChannelTypeKind = Types::Coercible::Symbol.default(:routing_key).enum(:routing_key, :queue)
+ ExchangeTypeKind = Types::Coercible::Symbol.enum(:topic, :fanout, :default, :direct, :headers)
MessageDeliveryModeKind = Types::Coercible::Integer.enum(1, 2)
RoutingKeyKind = Types::Coercible::String
RoutingKeyKinds = Types::Array.of(RoutingKeyKind)
QueueName = Types::Coercible::String
- AmqpBindingVersionKind =
- Types::Coercible::String.default('0.2.0').enum('0.2.0')
+ AmqpBindingVersionKind = Types::Coercible::String.default('0.2.0').enum('0.2.0')
OperationNameType = Types::String | Types::Symbol
# PatternedFieldName = String.constrained(format: /^[A-Za-z0-9_\-]+$/)
diff --git a/lib/event_source/uris/uri.rb b/lib/event_source/uris/uri.rb
index d7ab61f3..e04e622e 100644
--- a/lib/event_source/uris/uri.rb
+++ b/lib/event_source/uris/uri.rb
@@ -4,8 +4,15 @@ module EventSource
module Uris
# Class for Uri
class Uri
+ attr_reader :uri
+
def initialize(uri:)
- (uri.is_a? ::URI) ? uri : ::URI.parse(uri)
+ @uri = uri
+ (@uri.is_a? ::URI) ? @uri : ::URI.parse(@uri)
+ end
+
+ def to_s
+ @uri.to_s
end
end
end
diff --git a/spec/event_source/async_api/contracts/message_binding_contract_spec.rb b/spec/event_source/async_api/contracts/message_binding_contract_spec.rb
new file mode 100644
index 00000000..733d6095
--- /dev/null
+++ b/spec/event_source/async_api/contracts/message_binding_contract_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe EventSource::AsyncApi::Contracts::MessageBindingContract do
+ let(:content_encoding) { 'gzip' }
+ let(:message_type) { 'user.signup' }
+ let(:binding_version) { '0.3.0' }
+
+ let(:optional_params) do
+ { content_encoding: content_encoding, message_type: message_type, binding_version: binding_version }
+ end
+
+ describe '#call' do
+ subject(:message_binding) { described_class.new }
+
+ context 'Given empty parameters' do
+ it 'returns monad success' do
+ expect(message_binding.call({}).success?).to be_truthy
+ end
+ end
+
+ context 'Given optional only parameters' do
+ it 'returns monad success' do
+ expect(message_binding.call(optional_params).success?).to be_truthy
+ end
+
+ it 'all input params are returned' do
+ expect(message_binding.call(optional_params).to_h).to eq optional_params
+ end
+ end
+ end
+end
diff --git a/spec/event_source/async_api/contracts/message_contract_spec.rb b/spec/event_source/async_api/contracts/message_contract_spec.rb
index 09fbf058..5358d7d4 100644
--- a/spec/event_source/async_api/contracts/message_contract_spec.rb
+++ b/spec/event_source/async_api/contracts/message_contract_spec.rb
@@ -3,104 +3,97 @@
require 'spec_helper'
RSpec.describe EventSource::AsyncApi::Contracts::MessageContract do
- let(:name) { 'UserSignup' }
- let(:title) { 'User signup' }
- let(:summary) { 'Action to sign a user up.' }
- let(:description) { 'A longer description' }
- let(:contentType) { 'application/json' }
- let(:tags) do
- [{ name: 'user' }, { name: 'signup' }, { name: 'register' }]
- end
+ let(:occurred_at_property) { { type: 'string', description: 'Message timestamp' } }
+ let(:correlation_id_property) { { type: 'string', description: 'Correlation ID set by application' } }
- let(:header_type) { 'object' }
- let(:header_correlation_id) do
- { description: 'Correlation ID set by application', type: 'string' }
+ let(:header_schema_object_properties) do
+ { correlation_id: correlation_id_property, occurred_at: occurred_at_property }
end
- let(:application_instance_id) do
+ let(:header_schema_object_type) { 'object' }
+ let(:header_schema_object_required) { %w[correlation_id] }
+
+ let(:header_schema_object) do
{
- description:
- 'Unique identifier for a given instance of the publishing application',
- type: 'string'
+ type: header_schema_object_type,
+ required: header_schema_object_required,
+ properties: header_schema_object_properties
}
end
- let(:header_properties) do
+
+ let(:header_schema_format) { 'application/vnd.apache.avro+json;version=1.9.0' }
+ let(:header_schema) { { schema_format: header_schema_format, schema: header_schema_object } }
+
+ let(:provider_property) { { type: 'string', description: 'Third party OAuth service that authenticates account' } }
+ let(:uid_property) { { type: 'string', description: 'Provider-assigned unique account identifier' } }
+
+ let(:payload_schema_object_properties) { { provider: provider_property, uid: uid_property } }
+ let(:payload_schema_object_type) { 'object' }
+ let(:payload_schema_object_required) { %w[provider uid] }
+
+ let(:payload_schema_object) do
{
- correlation_id: header_correlation_id,
- application_instance_id: application_instance_id
+ type: payload_schema_object_type,
+ required: payload_schema_object_required,
+ properties: payload_schema_object_properties
}
end
- let(:headers) { { type: header_type, properties: header_properties } }
- let(:payload_type) { 'object' }
- let(:user) { { "$ref": '#/components/schemas/userCreate' } }
- let(:signup) { { "$ref": '#/components/schemas/signup' } }
- let(:payload_properties) { { user: user, signup: signup } }
+ let(:payload_schema_format) { 'application/vnd.apache.avro+json;version=1.9.0' }
+ let(:payload_schema) { { schema_format: payload_schema_format, schema: payload_schema_object } }
- let(:correlation_id) do
- { description: 'Correlation ID set by application', type: 'string' }
- end
+ let(:content_type) { 'application/json' }
+ let(:name) { 'UserSignup' }
+ let(:title) { 'User signup' }
+ let(:summary) { 'Action to sign a user up.' }
+ let(:description) { 'A longer description' }
+ let(:tags) { [{ name: 'user' }, { name: 'signup' }, { name: 'register' }] }
- let(:payload) { { type: payload_type, properties: payload_properties } }
- let(:correlation_id) do
- {
- description: 'Default Correlation ID',
- location: '$message.header#/correlation_id'
- }
+ let(:correlation_id) { { description: 'Default Correlation ID', location: '$message.header#/correlation_id' } }
+
+ let(:content_encoding) { 'gzip' }
+ let(:message_type) { 'user.signup' }
+ let(:binding_version) { '0.3.0' }
+
+ let(:message_binding) do
+ { content_encoding: content_encoding, message_type: message_type, binding_version: binding_version }
end
- let(:traits) { [{ "$ref": '#/components/messageTraits/commonHeaders' }] }
- let(:schema_format) { nil }
- let(:content_type) { nil }
- let(:external_docs) { [] }
- let(:bindings) { nil }
- let(:examples) { nil }
+ let(:external_docs) { [{ description: 'Version 1 message', url: 'http://example.com' }] }
+ let(:traits) { [{ content_type: content_type }] }
let(:optional_params) do
{
- headers: headers,
- payload: payload,
+ headers: header_schema,
+ payload: payload_schema,
+ correlation_id: correlation_id,
+ content_type: content_type,
name: name,
title: title,
summary: summary,
description: description,
- traits: traits,
tags: tags,
- schema_format: schema_format,
- contentType: content_type,
+ bindings: message_binding,
external_docs: external_docs,
- bindings: bindings,
- examples: examples
+ traits: traits
}
end
describe '#call' do
+ subject(:message) { described_class.new }
+
context 'Given empty parameters' do
- it { expect(subject.call({}).success?).to be_truthy }
+ it 'returns monad success' do
+ expect(message.call({}).success?).to be_truthy
+ end
end
- context 'Given valid parameters' do
- context 'and optional parameters' do
- it 'should successfully return all optional params as attributes' do
- result = subject.call(optional_params)
-
- expect(result.success?).to be_truthy
- expect(result.to_h).to eq optional_params
-
- expect(result[:headers]).to eq headers
- expect(result[:payload]).to eq payload
- expect(result[:name]).to eq name
- expect(result[:title]).to eq title
- expect(result[:summary]).to eq summary
- expect(result[:description]).to eq description
- expect(result[:tags]).to eq tags
- expect(result[:traits]).to eq traits
-
- expect(result[:schema_format]).to eq schema_format
- expect(result[:contentType]).to eq content_type
- expect(result[:external_docs]).to eq external_docs
- expect(result[:bindings]).to eq bindings
- expect(result[:examples]).to eq examples
- end
+ context 'Given optional only parameters' do
+ it 'returns monad success' do
+ expect(message.call(optional_params).success?).to be_truthy
+ end
+
+ it 'all input params are returned' do
+ expect(message.call(optional_params).to_h).to eq optional_params
end
end
end
diff --git a/spec/event_source/async_api/contracts/message_trait_contract_spec.rb b/spec/event_source/async_api/contracts/message_trait_contract_spec.rb
new file mode 100644
index 00000000..59005b5d
--- /dev/null
+++ b/spec/event_source/async_api/contracts/message_trait_contract_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe EventSource::AsyncApi::Contracts::MessageTraitContract do
+ let(:occurred_at_property) { { type: 'string', description: 'Message timestamp' } }
+ let(:correlation_id_property) { { type: 'string', description: 'Correlation ID set by application' } }
+
+ let(:header_schema_object_properties) do
+ { correlation_id: correlation_id_property, occurred_at: occurred_at_property }
+ end
+ let(:header_schema_object_type) { 'object' }
+ let(:header_schema_object_required) { %w[correlation_id] }
+
+ let(:header_schema_object) do
+ {
+ type: header_schema_object_type,
+ required: header_schema_object_required,
+ properties: header_schema_object_properties
+ }
+ end
+
+ let(:header_schema_format) { 'application/vnd.apache.avro+json;version=1.9.0' }
+ let(:header_schema) { { schema_format: header_schema_format, schema: header_schema_object } }
+
+ let(:content_type) { 'application/json' }
+ let(:name) { 'UserSignup' }
+ let(:title) { 'User signup' }
+ let(:summary) { 'Action to sign a user up.' }
+ let(:description) { 'A longer description' }
+ let(:tags) { [{ name: 'user' }, { name: 'signup' }, { name: 'register' }] }
+
+ let(:correlation_id) { { description: 'Default Correlation ID', location: '$message.header#/correlation_id' } }
+
+ let(:content_encoding) { 'gzip' }
+ let(:message_type) { 'user.signup' }
+ let(:binding_version) { '0.3.0' }
+
+ let(:message_binding) do
+ { content_encoding: content_encoding, message_type: message_type, binding_version: binding_version }
+ end
+
+ let(:external_docs) { [{ description: 'Version 1 message', url: 'http://example.com' }] }
+
+
+ let(:optional_params) do
+ {
+ headers: header_schema,
+ correlation_id: correlation_id,
+ content_type: content_type,
+ name: name,
+ title: title,
+ summary: summary,
+ description: description,
+ tags: tags,
+ bindings: message_binding,
+ external_docs: external_docs
+ }
+ end
+
+ describe '#call' do
+ subject(:message_trait) { described_class.new }
+
+ context 'Given empty parameters' do
+ it 'returns monad success' do
+ expect(message_trait.call({}).success?).to be_truthy
+ end
+ end
+
+ context 'Given optional only parameters' do
+ it 'returns monad success' do
+ expect(message_trait.call(optional_params).success?).to be_truthy
+ end
+
+ it 'all input params are returned' do
+ expect(message_trait.call(optional_params).to_h).to eq optional_params
+ end
+ end
+ end
+end
diff --git a/spec/event_source/async_api/contracts/schema_contract_spec.rb b/spec/event_source/async_api/contracts/schema_contract_spec.rb
new file mode 100644
index 00000000..632abd7f
--- /dev/null
+++ b/spec/event_source/async_api/contracts/schema_contract_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe EventSource::AsyncApi::Contracts::SchemaContract do
+ let(:type) { 'object' }
+ let(:required) { %w[correlation_id] }
+
+ let(:occurred_at_property) { { type: 'string', description: 'Message timestamp' } }
+ let(:correlation_id_property) { { type: 'string', description: 'Correlation ID set by application' } }
+ let(:properties) { { correlation_id: correlation_id_property, occurred_at: occurred_at_property } }
+ let(:schema) { { type: type, required: required, properties: properties } }
+
+ let(:schema_format) { 'application/vnd.apache.avro+json;version=1.9.0' }
+
+ let(:optional_params) { { schema_format: schema_format, schema: schema } }
+
+ describe '#call' do
+ subject(:schema_instance) { described_class.new }
+
+ context 'Given empty parameters' do
+ it 'returns monad success' do
+ expect(schema_instance.call({}).success?).to be_truthy
+ end
+ end
+
+ context 'Given optional only parameters' do
+ it 'returns monad success' do
+ expect(schema_instance.call(optional_params).success?).to be_truthy
+ end
+
+ it 'all input params are returned' do
+ expect(schema_instance.call(optional_params).to_h).to eq optional_params
+ end
+ end
+ end
+end
diff --git a/spec/event_source/async_api/contracts/schema_object_contract_spec.rb b/spec/event_source/async_api/contracts/schema_object_contract_spec.rb
new file mode 100644
index 00000000..fe750b51
--- /dev/null
+++ b/spec/event_source/async_api/contracts/schema_object_contract_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe EventSource::AsyncApi::Contracts::SchemaObjectContract do
+ let(:type) { 'object' }
+ let(:required) { %w[correlation_id] }
+
+ let(:occurred_at_property) { { type: 'string', description: 'Message timestamp' } }
+ let(:correlation_id_property) { { type: 'string', description: 'Correlation ID set by application' } }
+ let(:properties) { { correlation_id: correlation_id_property, occurred_at: occurred_at_property } }
+
+ let(:required_params) { { type: type } }
+ let(:optional_params) { { required: required, properties: properties } }
+ let(:all_params) { required_params.merge(optional_params) }
+
+ describe '#call' do
+ subject(:schema_object) { described_class.new }
+
+ context 'Given empty parameters' do
+ it 'returns monad failure' do
+ expect(schema_object.call({}).failure?).to be_truthy
+ end
+ end
+
+ context 'Given optional only parameters' do
+ it 'returns monad failure' do
+ expect(schema_object.call(optional_params).failure?).to be_truthy
+ end
+ end
+
+ context 'Given required parameters only' do
+ it 'returns monad success' do
+ expect(schema_object.call(required_params).success?).to be_truthy
+ end
+
+ it 'all input params are returned' do
+ expect(schema_object.call(required_params).to_h).to eq required_params
+ end
+ end
+
+ context 'Given all required and optional parameters' do
+ it 'returns monad success' do
+ expect(schema_object.call(all_params).success?).to be_truthy
+ end
+
+ it 'all input params are returned' do
+ expect(schema_object.call(all_params).to_h).to eq all_params
+ end
+ end
+
+ context 'Given a required property key thats not defined in properties hash' do
+ let(:undefined_property) { 'undefined_property' }
+ let(:invalid_params) { all_params.merge({ required: [undefined_property] }) }
+ let(:error) { { required: { 0 => ['undefined_property: not defined in properties'] } } }
+
+ it 'returns monad failure' do
+ expect(schema_object.call(invalid_params).failure?).to be_truthy
+ end
+
+ it 'returns an error for the undefined property' do
+ expect(schema_object.call(invalid_params).errors.to_h).to eq error
+ end
+ end
+ end
+end
diff --git a/spec/event_source/async_api/message_binding_spec.rb b/spec/event_source/async_api/message_binding_spec.rb
new file mode 100644
index 00000000..254c0c81
--- /dev/null
+++ b/spec/event_source/async_api/message_binding_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe EventSource::AsyncApi::MessageBinding do
+ subject(:message_binding) { described_class }
+
+ let(:content_encoding) { 'gzip' }
+ let(:message_type) { 'user.signup' }
+ let(:binding_version) { '0.3.0' }
+
+ let(:valid_params) do
+ { content_encoding: content_encoding, message_type: message_type, binding_version: binding_version }
+ end
+
+ context 'Given params that pass contract validation' do
+ let(:validated_params) { EventSource::AsyncApi::Contracts::MessageBindingContract.new.call(valid_params).to_h }
+
+ it 'it returns an entity instance' do
+ expect(message_binding.new(validated_params)).to be_a message_binding
+ end
+
+ it 'and all input params are populated' do
+ expect(message_binding.new(validated_params).to_h).to eq valid_params
+ end
+ end
+end
diff --git a/spec/event_source/async_api/message_spec.rb b/spec/event_source/async_api/message_spec.rb
new file mode 100644
index 00000000..20b85513
--- /dev/null
+++ b/spec/event_source/async_api/message_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe EventSource::AsyncApi::Message do
+ subject(:message) { described_class }
+
+ let(:occurred_at_property) { { type: 'string', description: 'Message timestamp' } }
+ let(:correlation_id_property) { { type: 'string', description: 'Correlation ID set by application' } }
+
+ let(:header_schema_object_properties) do
+ { correlation_id: correlation_id_property, occurred_at: occurred_at_property }
+ end
+ let(:header_schema_object_type) { 'object' }
+ let(:header_schema_object_required) { %w[correlation_id] }
+
+ let(:header_schema_object) do
+ {
+ type: header_schema_object_type,
+ required: header_schema_object_required,
+ properties: header_schema_object_properties
+ }
+ end
+
+ let(:header_schema_format) { 'application/vnd.apache.avro+json;version=1.9.0' }
+ let(:header_schema) { { schema_format: header_schema_format, schema: header_schema_object } }
+
+ let(:provider_property) { { type: 'string', description: 'Third party OAuth service that authenticates account' } }
+ let(:uid_property) { { type: 'string', description: 'Provider-assigned unique account identifier' } }
+
+ let(:payload_schema_object_properties) { { provider: provider_property, uid: uid_property } }
+ let(:payload_schema_object_type) { 'object' }
+ let(:payload_schema_object_required) { %w[provider uid] }
+
+ let(:payload_schema_object) do
+ {
+ type: payload_schema_object_type,
+ required: payload_schema_object_required,
+ properties: payload_schema_object_properties
+ }
+ end
+
+ let(:payload_schema_format) { 'application/vnd.apache.avro+json;version=1.9.0' }
+ let(:payload_schema) { { schema_format: payload_schema_format, schema: payload_schema_object } }
+
+ let(:content_type) { 'application/json' }
+ let(:name) { 'UserSignup' }
+ let(:title) { 'User signup' }
+ let(:summary) { 'Action to sign a user up.' }
+ let(:description) { 'A longer description' }
+ let(:tags) { [{ name: 'user' }, { name: 'signup' }, { name: 'register' }] }
+
+ let(:correlation_id) { { description: 'Default Correlation ID', location: '$message.header#/correlation_id' } }
+
+ let(:content_encoding) { 'gzip' }
+ let(:message_type) { 'user.signup' }
+ let(:binding_version) { '0.3.0' }
+
+ let(:message_binding) do
+ { content_encoding: content_encoding, message_type: message_type, binding_version: binding_version }
+ end
+
+ let(:external_docs) { [{ description: 'Version 1 message', url: 'http://example.com' }] }
+ let(:traits) { [{ content_type: content_type }] }
+
+ let(:all_params) do
+ {
+ headers: header_schema,
+ payload: payload_schema,
+ correlation_id: correlation_id,
+ content_type: content_type,
+ name: name,
+ title: title,
+ summary: summary,
+ description: description,
+ tags: tags,
+ bindings: message_binding,
+ external_docs: external_docs,
+ traits: traits
+ }
+ end
+
+ context 'Given validated all params' do
+ let(:validated_params) { EventSource::AsyncApi::Contracts::MessageContract.new.call(all_params).to_h }
+
+ it 'it returns an entity instance' do
+ expect(message.new(validated_params)).to be_a message
+ end
+
+ it 'and all input params are populated' do
+ expect(message.new(validated_params).to_h).to eq all_params
+ end
+ end
+end
diff --git a/spec/event_source/async_api/message_trait_spec.rb b/spec/event_source/async_api/message_trait_spec.rb
new file mode 100644
index 00000000..74a2214f
--- /dev/null
+++ b/spec/event_source/async_api/message_trait_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe EventSource::AsyncApi::MessageTrait do
+ subject(:message) { described_class }
+
+ let(:occurred_at_property) { { type: 'string', description: 'Message timestamp' } }
+ let(:correlation_id_property) { { type: 'string', description: 'Correlation ID set by application' } }
+
+ let(:header_schema_object_properties) do
+ { correlation_id: correlation_id_property, occurred_at: occurred_at_property }
+ end
+ let(:header_schema_object_type) { 'object' }
+ let(:header_schema_object_required) { %w[correlation_id] }
+
+ let(:header_schema_object) do
+ {
+ type: header_schema_object_type,
+ required: header_schema_object_required,
+ properties: header_schema_object_properties
+ }
+ end
+
+ let(:header_schema_format) { 'application/vnd.apache.avro+json;version=1.9.0' }
+ let(:header_schema) { { schema_format: header_schema_format, schema: header_schema_object } }
+
+ let(:provider_property) { { type: 'string', description: 'Third party OAuth service that authenticates account' } }
+ let(:uid_property) { { type: 'string', description: 'Provider-assigned unique account identifier' } }
+
+
+ let(:content_type) { 'application/json' }
+ let(:name) { 'UserSignup' }
+ let(:title) { 'User signup' }
+ let(:summary) { 'Action to sign a user up.' }
+ let(:description) { 'A longer description' }
+ let(:tags) { [{ name: 'user' }, { name: 'signup' }, { name: 'register' }] }
+
+ let(:correlation_id) { { description: 'Default Correlation ID', location: '$message.header#/correlation_id' } }
+
+ let(:content_encoding) { 'gzip' }
+ let(:message_type) { 'user.signup' }
+ let(:binding_version) { '0.3.0' }
+
+ let(:message_binding) do
+ { content_encoding: content_encoding, message_type: message_type, binding_version: binding_version }
+ end
+
+ let(:external_docs) { [{ description: 'Version 1 message', url: 'http://example.com' }] }
+
+ let(:all_params) do
+ {
+ headers: header_schema,
+ correlation_id: correlation_id,
+ content_type: content_type,
+ name: name,
+ title: title,
+ summary: summary,
+ description: description,
+ tags: tags,
+ bindings: message_binding,
+ external_docs: external_docs,
+ }
+ end
+
+ context 'Given validated all params' do
+ let(:validated_params) { EventSource::AsyncApi::Contracts::MessageContract.new.call(all_params).to_h }
+
+ it 'it returns an entity instance' do
+ expect(message.new(validated_params)).to be_a message
+ end
+
+ it 'and all input params are populated' do
+ expect(message.new(validated_params).to_h).to eq all_params
+ end
+ end
+end
diff --git a/spec/event_source/async_api/schema_object_spec.rb b/spec/event_source/async_api/schema_object_spec.rb
new file mode 100644
index 00000000..b4279d64
--- /dev/null
+++ b/spec/event_source/async_api/schema_object_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe EventSource::AsyncApi::SchemaObject do
+ subject(:schema_object) { described_class }
+
+ let(:type) { 'object' }
+ let(:required) { %w[correlation_id] }
+
+ let(:occurred_at_property) { { type: 'string', description: 'Message timestamp' } }
+ let(:correlation_id_property) { { type: 'string', description: 'Correlation ID set by application' } }
+ let(:properties) { { correlation_id: correlation_id_property, occurred_at: occurred_at_property } }
+
+ let(:required_params) { { type: type } }
+ let(:optional_params) { { required: required, properties: properties } }
+ let(:all_params) { required_params.merge(optional_params) }
+
+ context 'Given validated required params' do
+ let(:validated_params) { EventSource::AsyncApi::Contracts::SchemaObjectContract.new.call(required_params).to_h }
+
+ it 'it returns an entity instance' do
+ expect(schema_object.new(validated_params)).to be_a schema_object
+ end
+
+ it 'and all input params are populated' do
+ expect(schema_object.new(validated_params).to_h).to eq required_params
+ end
+ end
+
+ context 'Given validated all params' do
+ let(:validated_params) { EventSource::AsyncApi::Contracts::SchemaObjectContract.new.call(all_params).to_h }
+
+ it 'it returns an entity instance' do
+ expect(schema_object.new(validated_params)).to be_a schema_object
+ end
+
+ it 'and all input params are populated' do
+ expect(schema_object.new(validated_params).to_h).to eq all_params
+ end
+ end
+end
diff --git a/spec/event_source/async_api/schema_spec.rb b/spec/event_source/async_api/schema_spec.rb
new file mode 100644
index 00000000..748a270b
--- /dev/null
+++ b/spec/event_source/async_api/schema_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe EventSource::AsyncApi::Schema do
+ subject(:schema) { described_class }
+
+ let(:type) { 'object' }
+ let(:required) { %w[correlation_id] }
+
+ let(:occurred_at_property) { { type: 'string', description: 'Message timestamp' } }
+ let(:correlation_id_property) { { type: 'string', description: 'Correlation ID set by application' } }
+ let(:properties) { { correlation_id: correlation_id_property, occurred_at: occurred_at_property } }
+ let(:schema_object) { { type: type, required: required, properties: properties } }
+
+ let(:schema_format) { 'application/vnd.apache.avro+json;version=1.9.0' }
+
+ let(:valid_params) { { schema_format: schema_format, schema: schema_object } }
+
+ context 'Given params that pass contract validation' do
+ let(:validated_params) { EventSource::AsyncApi::Contracts::SchemaContract.new.call(valid_params).to_h }
+
+ it 'it returns an entity instance' do
+ expect(schema.new(validated_params)).to be_a schema
+ end
+
+ it 'and all input params are populated' do
+ expect(schema.new(validated_params).to_h).to eq valid_params
+ end
+ end
+end