diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 1ad899d..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 076b2db..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: 2 -updates: -- package-ecosystem: bundler - directory: "/" - schedule: - interval: daily - time: "04:00" - timezone: Europe/Madrid - open-pull-requests-limit: 10 - ignore: - - dependency-name: rubocop - versions: - - 1.10.0 - - 1.11.0 - - 1.12.0 - - 1.12.1 - - 1.9.0 - - 1.9.1 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml deleted file mode 100644 index 73232e3..0000000 --- a/.github/workflows/coverage.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: coverage -on: [push, pull_request] - -env: - COVERAGE: 1 - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.4 - bundler-cache: true - - run: | - bundle exec rake test spinach - - uses: coverallsapp/github-action@v2 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml deleted file mode 100644 index d8a4cff..0000000 --- a/.github/workflows/danger.yml +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: danger -on: - pull_request: - types: [opened, reopened, edited, synchronize] -jobs: - danger: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.4 - bundler-cache: true - - name: Run Danger - run: | - # the personal token is public, this is ok, base64 encode to avoid tripping Github - TOKEN=$(echo -n Z2hwX0xNQ3VmanBFeTBvYkZVTWh6NVNqVFFBOEUxU25abzBqRUVuaAo= | base64 --decode) - DANGER_GITHUB_API_TOKEN=$TOKEN bundle exec danger --verbose diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 7c6d5b3..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: lint -on: [push, pull_request] -jobs: - rubocop: - name: RuboCop - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '2.7' - bundler-cache: true - - run: bundle exec rubocop - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index ff75f78..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,24 +0,0 @@ ---- -name: test -on: [push, pull_request] -jobs: - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - entry: - - { ruby: '3.4' } - - { ruby: '3.2' } - - { ruby: '2.7' } - - { ruby: 'ruby-head', allowed-failure: true } - - { ruby: 'jruby-head', allowed-failure: true } - name: test (${{ matrix.entry.ruby }}) - steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.entry.ruby }} - bundler-cache: true - - run: bundle exec rake test spinach - continue-on-error: ${{ matrix.entry.allowed-failure || false }} diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 4b2b4ab..0000000 --- a/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -*.gem -*.rbc -.bundle -.config -.yardoc -InstalledFiles -_yardoc -coverage -doc/ -lib/bundler/man -pkg -rdoc -spec/reports -test/tmp -test/version_tmp -tmp -examples/william.rb -Gemfile.lock diff --git a/.rubocop.yml b/.rubocop.yml deleted file mode 100644 index 95b1856..0000000 --- a/.rubocop.yml +++ /dev/null @@ -1,15 +0,0 @@ -inherit_from: .rubocop_todo.yml - -plugins: - - rubocop-rake - - rubocop-minitest - -AllCops: - NewCops: enable - TargetRubyVersion: 2.3 - -Metrics: - Enabled: false - -Style/FrozenStringLiteralComment: - Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml deleted file mode 100644 index 90890bc..0000000 --- a/.rubocop_todo.yml +++ /dev/null @@ -1,61 +0,0 @@ -# This configuration was generated by -# `rubocop --auto-gen-config` -# on 2025-09-10 13:55:47 UTC using RuboCop version 1.80.2. -# The point is for the user to remove these configuration records -# one by one as the offenses are removed from the code base. -# Note that changes in the inspected code, or installation of new -# versions of RuboCop, may require this file to be generated again. - -# Offense count: 1 -# Configuration parameters: Severity. -Gemspec/RequiredRubyVersion: - Exclude: - - 'hyperclient.gemspec' - -# Offense count: 1 -# Configuration parameters: AllowComments, AllowEmptyLambdas. -Lint/EmptyBlock: - Exclude: - - 'test/hyperclient/entry_point_test.rb' - -# Offense count: 4 -# Configuration parameters: AllowedParentClasses. -Lint/MissingSuper: - Exclude: - - 'lib/hyperclient/attributes.rb' - - 'lib/hyperclient/entry_point.rb' - - 'lib/hyperclient/link_collection.rb' - - 'lib/hyperclient/resource_collection.rb' - -# Offense count: 1 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowedMethods, InferNonNilReceiver, AdditionalNilMethods. -# AllowedMethods: instance_of?, kind_of?, is_a?, eql?, respond_to?, equal? -# AdditionalNilMethods: present?, blank?, try, try! -Lint/RedundantSafeNavigation: - Exclude: - - 'lib/hyperclient/link.rb' - -# Offense count: 3 -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle, EnforcedStyleForClasses, EnforcedStyleForModules. -# SupportedStyles: nested, compact -# SupportedStylesForClasses: ~, nested, compact -# SupportedStylesForModules: ~, nested, compact -Style/ClassAndModuleChildren: - Exclude: - - 'features/steps/api_navigation.rb' - - 'features/steps/default_config.rb' - - 'features/support/env.rb' - -# Offense count: 5 -# Configuration parameters: AllowedConstants. -Style/Documentation: - Exclude: - - 'spec/**/*' - - 'test/**/*' - - 'features/steps/api_navigation.rb' - - 'features/steps/default_config.rb' - - 'features/support/api.rb' - - 'features/support/env.rb' - - 'features/support/fixtures.rb' diff --git a/.yardopts b/.yardopts deleted file mode 100644 index b73f188..0000000 --- a/.yardopts +++ /dev/null @@ -1,8 +0,0 @@ ---title "Hyperclient" ---readme README.md ---protected ---private ---plugin tomdoc -lib -- -[A-Z]*.* diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 97f38a0..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,143 +0,0 @@ -## Changelog - -### 2.0.1 (Next) - -* [#298](https://github.com/codegram/hyperclient/pull/300): Upgraded RuboCop to 1.80.2 - [@dblock](https://github.com/dblock). -* [#298](https://github.com/codegram/hyperclient/pull/298): Upgraded RuboCop to 1.63.5 - [@dblock](https://github.com/dblock). -* Your contribution here. - -### 2.0.0 (2024/02/01) - -* [#268](https://github.com/codegram/hyperclient/pull/268): Replace Travis-CI with GHA - [@dblock](https://github.com/dblock). -* [#269](https://github.com/codegram/hyperclient/pull/269): Re-add code coverage - [@dblock](https://github.com/dblock). -* [#270](https://github.com/codegram/hyperclient/pull/270): Upgrade RuboCop to 1.45 - [@dblock](https://github.com/dblock). -* [#271](https://github.com/codegram/hyperclient/pull/271): Test against Ruby 3.2 - [@dblock](https://github.com/dblock). -* [#285](https://github.com/codegram/hyperclient/pull/285): Support Faraday 2.x - [@dblock](https://github.com/dblock). - -### 1.0.1 (2021/01/02) - -* [#193](https://github.com/codegram/hyperclient/pull/193): Auto-paginate collections - [@dblock](https://github.com/dblock). -* [#163](https://github.com/codegram/hyperclient/pull/163): Test against Faraday 0.9, 0.17 and 1.0+ - [@dblock](https://github.com/dblock). -* [#199](https://github.com/codegram/hyperclient/pull/199): Use digest_auth from faraday-digestauth - [@dblock](https://github.com/dblock). - -### 1.0.0 (2021/01/02) - -* NOTE: **⚠ This version has been yanked ⚠** - [@dblock](https://github.com/dblock). - -### 0.9.3 (2020/05/14) - -* [#149](https://github.com/codegram/hyperclient/pull/149): Address Faraday warnings - [@yuki24](https://github.com/yuki24). -* [#160](https://github.com/codegram/hyperclient/pull/160): Require newer 'faraday-digestauth' - [@dblock](https://github.com/dblock). - -### 0.9.2 (2020/05/12) - -* NOTE: **⚠ This version has been yanked ⚠** - [@dblock](https://github.com/dblock). - -### 0.9.1 (2019/08/25) - -* NOTE: **⚠ This version is no longer tested with Ruby < 2.3 ⚠** - [@ivoanjo](https://github.com/ivoanjo). - -* [#135](https://github.com/codegram/hyperclient/pull/135): Fix validation for empty body responses - [@paulocdf](https://github.com/paulocdf). -* [#139](https://github.com/codegram/hyperclient/pull/139): Test `hyperclient` against newer versions of MRI Ruby, up until 2.6.x - [@mrcasals](https://github.com/mrcasals). -* [#141](https://github.com/codegram/hyperclient/pull/141): Replace `uri_template` with `addressable` library - [@mrcasals](https://github.com/mrcasals). -* [#136](https://github.com/codegram/hyperclient/pull/136), [#146](https://github.com/codegram/hyperclient/pull/146): Upgraded Danger plugins - [@dblock](https://github.com/dblock), [@ivoanjo](https://github.com/ivoanjo). - -### 0.9.0 (2018/01/10) - -* [#133](https://github.com/codegram/hyperclient/pull/133): Removed futuroscope - [@dblock](https://github.com/dblock). -* [#131](https://github.com/codegram/hyperclient/pull/131): Upgrade to RuboCop 0.50.0, fix Bundler's insecure git source warning - [@nebolsin](https://github.com/nebolsin). -* [#132](https://github.com/codegram/hyperclient/pull/132): Swapped yard dependency for danger-toc - [@dblock](https://github.com/dblock). - -### 0.8.6 (2017/08/27) - -* [#122](https://github.com/codegram/hyperclient/pull/122): Improve error message when server returns invalid data - [@ivoanjo](https://github.com/ivoanjo). -* [#125](https://github.com/codegram/hyperclient/pull/125): Add table of contents to readme and add note asking users to add their projects to the wiki - [@ivoanjo](https://github.com/ivoanjo). -* [#127](https://github.com/codegram/hyperclient/pull/127): Minor fixes: Fix warnings, and pry-byebug to dev Gemfile and tweak RuboCop execution - [@ivoanjo](https://github.com/ivoanjo). -* [#128](https://github.com/codegram/hyperclient/pull/128): Fix link delegation returning nil for field with value false - [@ivoanjo](https://github.com/ivoanjo). - -### 0.8.5 (2017/07/05) - -* [#120](https://github.com/codegram/hyperclient/pull/120): Replace non-working homepage link in gemspec - [@ivoanjo](https://github.com/ivoanjo). - -### 0.8.4 (2017/05/16) - -* [#117](https://github.com/codegram/hyperclient/issues/117): Require Faraday >= 0.9.0 in gemspec - [@ivoanjo](https://github.com/ivoanjo). - -### 0.8.3 (2017/03/30) - -* [#115](https://github.com/codegram/hyperclient/pull/115): Fix dropped values from queries by using FlatParamsEncoder - [@ivoanjo](https://github.com/ivoanjo). - -### 0.8.2 (2016/12/31) - -* NOTE: **⚠ This version is no longer tested with Ruby < 2.2 ⚠** - [@dblock](https://github.com/dblock). - -* [#105](https://github.com/codegram/hyperclient/pull/105), [#108](https://github.com/codegram/hyperclient/pull/108): Added Danger, PR linter - [@dblock](https://github.com/dblock). -* [#109](https://github.com/codegram/hyperclient/pull/109): Allow disabling asynchronous behavior per-instance - [@Talkdesk](https://github.com/Talkdesk). -* [#110](https://github.com/codegram/hyperclient/pull/110): Fixed ruby warnings - [@ivoanjo](https://github.com/ivoanjo). - -### 0.8.1 (2016/03/15) - -* [#97](https://github.com/codegram/hyperclient/issues/97): Fix: curies are no longer used as link shorteners - [@joshco](https://github.com/joshco), [@dblock](https://github.com/dblock). -* [#95](https://github.com/codegram/hyperclient/issues/95): Fix: re-add eager delegation for `resource.each` - [@dblock](https://github.com/dblock). - -### 0.7.2 (2015/08/23) - -* [#95](https://github.com/codegram/hyperclient/issues/95): Fix: re-add eager delegation for `resource.x._embeddded.x` - [@dblock](https://github.com/dblock). - -### 0.7.1 (2015/08/15) - -* [#89](https://github.com/codegram/hyperclient/issues/89): Added `Hyperclient::Resource#fetch` - [@alabeduarte](https://github.com/alabeduarte). -* [#87](https://github.com/codegram/hyperclient/pull/87): Fix: eager delegation causes link skipping - [@dblock](https://github.com/dblock). - -### 0.7.0 (2015/02/23) - -* NOTE: **⚠ This version introduces several backwards incompatible changes. See [UPGRADING](UPGRADING.md) for details ⚠** - [@dblock](https://github.com/dblock). - -* [#80](https://github.com/codegram/hyperclient/pull/80): Faraday options can be passed to the connection on initialization - [@koenpunt](https://github.com/koenpunt). -* [#81](https://github.com/codegram/hyperclient/pull/81): The default Content-Type is now `application/hal+json` - [@koenpunt](https://github.com/koenpunt). - -### 0.6.1 (2014/10/17) - -* NOTE: **⚠ This version introduces several backwards incompatible changes. See [UPGRADING](UPGRADING.md) for details ⚠** - [@dblock](https://github.com/dblock). - -* [#51](https://github.com/codegram/hyperclient/issues/51), [#75](https://github.com/codegram/hyperclient/pull/75): Added support for setting headers and overriding or extending the default Faraday connection block before a connection is constructed - [@dblock](https://github.com/dblock). -* [#41](https://github.com/codegram/hyperclient/issues/41), [#73](https://github.com/codegram/hyperclient/pull/73): All Link HTTP methods now return a Resource, including `_get`, which has been aliased to `_resource`, `_post`, `_put`, `_patch`, `_head` and `_options` - [@dblock](https://github.com/dblock). -* [#72](https://github.com/codegram/hyperclient/pull/72): The default Faraday block now uses `Faraday::Response::RaiseError` and will cause HTTP errors to be raised as exceptions - [@dblock](https://github.com/dblock). -* [#77](https://github.com/codegram/hyperclient/pull/77): Added support for templated links with all optional arguments - [@dblock](https://github.com/dblock). - -### 0.5.0 (2014/10/01) - -* NOTE: **⚠ This version introduces several backwards incompatible changes. See [UPGRADING](UPGRADING.md) for details ⚠** - [@dblock](https://github.com/dblock). - -* [#63](https://github.com/codegram/hyperclient/pull/63): Navigational methods, including `links`, `get` or `post`, have been renamed to `_links`, `_get`, or `_post` respectively - [@dblock](https://github.com/dblock). -* [#64](https://github.com/codegram/hyperclient/issues/64): Added support for curies - [@dblock](https://github.com/dblock). -* [#58](https://github.com/codegram/hyperclient/issues/58): Automatically follow redirects - [@dblock](https://github.com/dblock). -* [#63](https://github.com/codegram/hyperclient/pull/63): You can omit the navigational elements, `api.links.products` is now equivalent to `api.products` - [@dblock](https://github.com/dblock). -* [#61](https://github.com/codegram/hyperclient/pull/61): Implemented RuboCop, Ruby-style linter - [@dblock](https://github.com/dblock). - -### 0.4.0 (2014/05/05) - -* [#54](https://github.com/codegram/hyperclient/pull/54): Support Faraday 0.9.0 - [@lucianapazos](https://github.com/lucianapazos). -* [#30](https://github.com/codegram/hyperclient/pull/30): Use futuroscope to run API calls in the background - [@josepjaume](https://github.com/josepjaume). - -### 0.3.2 (2013/12/20) - -* [#48](https://github.com/codegram/hyperclient/pull/48): Added support for fetch on the collection class - [@col](https://github.com/col). -* [#50](https://github.com/codegram/hyperclient/pull/50): Fixed Resource/Attributes mutating the response body - [@col](https://github.com/col). -* [#46](https://github.com/codegram/hyperclient/pull/46): Made response available inside Resource, provide access to status codes - [@benhamill](https://github.com/benhamill). -* [#43](https://github.com/codegram/hyperclient/pull/43): Fixed LinkCollection#include? - [@benhamill](https://github.com/benhamill). -* [#47](https://github.com/codegram/hyperclient/pull/47): Fixed uninitialized constant Hyperclient::Resource::Forwardable - [@benhamill](https://github.com/benhamill). -* [#39](https://github.com/codegram/hyperclient/pull/39): Exposed templated link properties - [@txus](https://github.com/txus). -* [#38](https://github.com/codegram/hyperclient/pull/38): Defaulted POST, PUT and PATCH parameters - [@bkeepers](https://github.com/bkeepers). -* [#37](https://github.com/codegram/hyperclient/pull/37): Fixed calling #flatten on an array of links - [@bkeepers](https://github.com/bkeepers). -* [#36](https://github.com/codegram/hyperclient/pull/36): Exposed link properties - [@bkeepers](https://github.com/bkeepers). -* [#31](https://github.com/codegram/hyperclient/pull/31): Allowed underscored attribute names other than the ones reserved by the HAL spec - [@karlin](https://github.com/karlin). -* [#29](https://github.com/codegram/hyperclient/pull/29): Handled JSON that includes a link with a null value - [@arbylee](https://github.com/arbylee). - -### 0.3.1 (2013/04/03) - -* [#27](https://github.com/codegram/hyperclient/pull/27): Added support for collections of links - [@rehevkor5](https://github.com/rehevkor5). - -### 0.3.0 (2013/02/03) - -* Initial public release - [@oriolgual](https://github.com/oriolgual). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index a4b627b..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,117 +0,0 @@ -Contributing to Hyperclient -=========================== - -Hyperclient is work of [many people](https://github.com/codegram/hyperclient/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/codegram/hyperclient/pulls), [propose features and discuss issues](https://github.com/codegram/hyperclient/issues). - -#### Fork the Project - -Fork the [project on Github](https://github.com/codegram/hyperclient) and check out your copy. - -``` -git clone https://github.com/contributor/hyperclient.git -cd hyperclient -git remote add upstream https://github.com/codegram/hyperclient.git -``` - -#### Create a Topic Branch - -Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. - -``` -git checkout master -git pull upstream master -git checkout -b my-feature-branch -``` - -#### Bundle Install and Test - -Ensure that you can build the project and run tests. - -``` -bundle install -bundle exec rake -``` - -#### Write Tests - -Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. Add to [test/hyperclient](test/hyperclient). - -We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. - -#### Write Code - -Implement your feature or bug fix. Don't forget to update the [README](README.md). - -Ruby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop), run `bundle exec rubocop` and fix any style issues highlighted. - -Make sure that `bundle exec rake` completes without errors. - -#### Commit Changes - -Make sure git knows your name and email address: - -``` -git config --global user.name "Your Name" -git config --global user.email "contributor@example.com" -``` - -Writing good commit logs is important. A commit log should describe what changed and why. - -``` -git add ... -git commit -``` - -#### Push - -``` -git push origin my-feature-branch -``` - -#### Make a Pull Request - -Go to https://github.com/contributor/hyperclient and select your feature branch. Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. - -#### Update CHANGELOG - -Update the [CHANGELOG](CHANGELOG.md) with the pull request number. A typical entry looks as follows. - -``` -* [#123](https://github.com/codegram/hyperclient/pull/123): Reticulated splines - [@contributor](https://github.com/contributor). -``` - -We like neat commits. Amend your previous commit and force push the changes. - -``` -git commit --amend -git push origin my-feature-branch -f -``` - -#### Rebase - -If you've been working on a change for a while, rebase with upstream/master often. - -``` -git fetch upstream -git rebase upstream/master -git push origin my-feature-branch -f -``` - -We like neat commits, please try to amend your previous commits and force push the changes. - -``` -git commit --amend -git push origin my-feature-branch -f -``` - -#### Check on Your Pull Request - -Go back to your pull request after a few minutes and see whether it passed muster with CI. Everything should look green, otherwise fix issues and amend your commit as described above. - -#### Be Patient - -It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there! - -#### Thank You - -Please do know that we really appreciate and value your time and work. We love you, really. diff --git a/Dangerfile b/Dangerfile deleted file mode 100644 index c7a9f9c..0000000 --- a/Dangerfile +++ /dev/null @@ -1,2 +0,0 @@ -changelog.check! -toc.check! diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 64e8cb6..0000000 --- a/Gemfile +++ /dev/null @@ -1,31 +0,0 @@ -source 'https://rubygems.org/' - -gemspec - -group :development do - gem 'growl' - gem 'guard' - gem 'guard-minitest' - gem 'guard-spinach' - gem 'pry' - gem 'pry-byebug', platforms: :ruby -end - -group :development, :test do - gem 'rake' - gem 'rubocop', '~> 1.81.1', require: false - gem 'rubocop-minitest', require: false - gem 'rubocop-rake', require: false - gem 'simplecov', require: false - gem 'simplecov-lcov', require: false -end - -group :test do - gem 'danger-changelog', '~> 0.7.0' - gem 'danger-toc', '~> 0.2.0' - gem 'minitest' - gem 'mocha' - gem 'rack-test' - gem 'spinach' - gem 'webmock' -end diff --git a/Guardfile b/Guardfile deleted file mode 100644 index aaaadf9..0000000 --- a/Guardfile +++ /dev/null @@ -1,13 +0,0 @@ -guard 'minitest' do - watch(%r{^test/(.*)_test\.rb}) - watch(%r{^lib/(.*)([^/]+)\.rb}) { |m| "test/#{m[1]}#{m[2]}_test.rb" } - watch(%r{^(.*)([^/]+)\.rb}) { |m| "test/#{m[1]}#{m[2]}_test.rb" } - watch(%r{^test/test_helper\.rb}) { 'test' } -end - -guard 'spinach' do - watch(%r{^features/(.*)\.feature}) - watch(%r{^features/steps/(.*)([^/]+)\.rb}) do |m| - "features/#{m[1]}#{m[2]}.feature" - end -end diff --git a/LICENSE b/LICENSE deleted file mode 100644 index b9ce605..0000000 --- a/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -Copyright (c) 2012-2018 Oriol Gual, Codegram Technologies and Contributors - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md deleted file mode 100644 index cf361d6..0000000 --- a/README.md +++ /dev/null @@ -1,256 +0,0 @@ -# Hyperclient - -[![Gem Version](http://img.shields.io/gem/v/hyperclient.svg)](http://badge.fury.io/rb/hyperclient) -[![Build Status](https://github.com/codegram/hyperclient/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/codegram/hyperclient/actions/workflows/test.yml) -[![Coverage Status](https://img.shields.io/coveralls/codegram/hyperclient.svg)](https://coveralls.io/r/codegram/hyperclient?branch=master) - -Hyperclient is a Hypermedia API client written in Ruby. It fully supports [JSON HAL](http://stateless.co/hal_specification.html). - -# Table of Contents - -- [Usage](#usage) - - [API Client](#api-client) - - [Resources and Attributes](#resources-and-attributes) - - [Links and Embedded Resources](#links-and-embedded-resources) - - [Templated Links](#templated-links) - - [Curies](#curies) - - [Attributes](#attributes) - - [HTTP](#http) -- [Testing Using Hyperclient](#testing-using-hyperclient) -- [Reference](#reference) -- [Hyperclient Users](#hyperclient-users) -- [Contributing](#contributing) -- [License](#license) - -# Usage - -The examples in this README use the [Splines Demo API](https://github.com/ruby-grape/grape-with-roar) running [here](https://grape-with-roar.herokuapp.com/api). Use version 1.x with Faraday 1.x, and version 2.x with Faraday 2.x. If you're upgrading from a previous version, please make sure to read [UPGRADING](UPGRADING.md). - -## API Client - -Create an API client. - -```ruby -require 'hyperclient' - -api = Hyperclient.new('https://grape-with-roar.herokuapp.com/api') -``` - -By default, Hyperclient adds `application/hal+json` as `Content-Type` and `Accept` headers. It will also send requests as JSON and parse JSON responses. Specify additional headers or authentication if necessary. - -```ruby -api = Hyperclient.new('https://grape-with-roar.herokuapp.com/api') do |client| - client.headers['Access-Token'] = 'token' -end -``` - -Hyperclient constructs a connection using typical [Faraday](http://github.com/lostisland/faraday) middleware for handling JSON requests and responses. You can specify additional Faraday middleware if necessary. - -```ruby -api = Hyperclient.new('https://grape-with-roar.herokuapp.com/api') do |client| - client.connection do |conn| - conn.use Faraday::Request::Instrumentation - end -end -``` - -You can pass options to the Faraday connection block in the `connection` block: - -```ruby -api = Hyperclient.new('https://grape-with-roar.herokuapp.com/api') do |client| - client.connection(ssl: { verify: false }) do |conn| - conn.use Faraday::Request::Instrumentation - end -end -``` - -Or when using the default connection configuration you can use `faraday_options`: - -```ruby -api = Hyperclient.new('https://grape-with-roar.herokuapp.com/api') do |client| - client.faraday_options = { ssl: { verify: false } } -end -``` - -You can build a new Faraday connection block without inheriting default middleware by specifying `default: false` in the `connection` block. - -```ruby -api = Hyperclient.new('https://grape-with-roar.herokuapp.com/api') do |client| - client.connection(default: false) do |conn| - conn.request :json - conn.response :json, content_type: /\bjson$/ - conn.adapter :net_http - end -end -``` - -You can modify headers or specify authentication after a connection has been created. Hyperclient supports Basic, Token or [Digest auth](https://github.com/bhaberer/faraday-digestauth) as well as many other Faraday extensions. - -```ruby -require 'faraday/digestauth' - -api = Hyperclient.new('https://grape-with-roar.herokuapp.com/api') do |client| - client.connection(default: false) do |conn| - conn.request :digest, 'username', 'password' - conn.request :json - conn.response :json, content_type: /\bjson$/ - conn.adapter :net_http - end -end -``` - -You can access the Faraday connection directly after it has been created and add middleware to it. As an example, you could use the [faraday-http-cache-middleware](https://github.com/plataformatec/faraday-http-cache). - -```ruby -api = Hyperclient.new('https://grape-with-roar.herokuapp.com/api') -api.connection.use :http_cache -``` - -## Resources and Attributes - -Hyperclient will fetch and discover the resources from your API and automatically paginate when possible. - -```ruby -api.splines.each do |spline| - puts "A spline with ID #{spline.uuid}." -end -``` - -Other methods, including `[]` and `fetch` are also available - -```ruby -api.splines.each do |spline| - puts "A spline with ID #{spline[:uuid]}." - puts "Maybe with reticulated: #{spline.fetch(:reticulated, '-- no reticulated')}" -end -``` - -## Links and Embedded Resources - -The splines example above followed a link called "splines". While you can, you do not need to specify the HAL navigational structure, including links or embedded resources. Hyperclient will resolve these for you. If you prefer, you can explicitly navigate the link structure via `_links`. In the following example the "splines" link leads to a collection of embedded splines. Invoking `api.splines` is equivalent to `api._links.splines._embedded.splines`. - -```ruby -api._links.splines -``` - -## Templated Links - -Templated links require variables to be expanded. For example, the demo API has a link called "spline" that requires a spline "uuid". - -```ruby -spline = api.spline(uuid: 'uuid') -puts "Spline #{spline.uuid} is #{spline.reticulated ? 'reticulated' : 'not reticulated'}." -``` - -Invoking `api.spline(uuid: 'uuid').reticulated` is equivalent to `api._links.spline._expand(uuid: 'uuid')._resource._attributes.reticulated`. - -The client is responsible for supplying all the necessary parameters. Templated links don't do any strict parameter name checking and don't support required vs. optional parameters. Parameters not declared by the API will be dropped and will not have any effect when passed to `_expand`. - -## Curies - -Curies are a suggested means by which to link documentation of a given resource. For example, the demo API contains very long links to images that use an "images" curie. - -```ruby -puts spline['image:thumbnail'] # => https://grape-with-roar.herokuapp.com/api/splines/uuid/images/thumbnail.jpg -puts spline.links._curies['image'].expand('thumbnail') # => /docs/images/thumbnail -``` - -## Attributes - -Resource attributes can also be accessed as a hash. - -```ruby -puts spline.to_h # => {"uuid" => "uuid", "reticulated" => true} -``` - -The above is equivalent to `spline._attributes.to_h`. - -## HTTP - -Hyperclient uses [Faraday](http://github.com/lostisland/faraday) under the hood to perform HTTP calls. You can call any valid HTTP method on any resource. - -For example, you can examine the API raw JSON by invoking `_get` and examining the `_response.body` hash. - -```ruby -api._get -api._response.body -``` - -Other methods, including `_head` or `_options` are also available. - -```ruby -spline = api.spline(uuid: 'uuid') -spline._head -spline._options -``` - -Invoke `_post` to create resources. - -```ruby -splines = api.splines -splines._post(uuid: 'new uuid', reticulated: false) -``` - -Invoke `_put` or `_patch` to update resources. - -```ruby -spline = api.spline(uuid: 'uuid') -spline._put(reticulated: true) -spline._patch(reticulated: true) -``` - -Invoke `_delete` to destroy a resource. - -``` -spline = api.spline(uuid: 'uuid') -spline._delete -``` - -HTTP methods always return a new instance of Resource. - -# Testing Using Hyperclient - -You can combine RSpec, Faraday::Adapter::Rack and Hyperclient to test your HAL API without having to ever examine the raw JSON response. - -```ruby -describe Acme::Api do - def app - Acme::App.instance - end - - let(:client) do - Hyperclient.new('http://example.org/api') do |client| - client.headers['Content-Type'] = 'application/json' - client.connection(default: false) do |conn| - conn.request :json - conn.response :json - conn.use Faraday::Adapter::Rack, app - end - end - end - - it 'splines returns 3 splines by default' do - expect(client.splines.count).to eq 3 - end -end -``` - -For a complete example refer to [this Splines Demo API test](https://github.com/ruby-grape/grape-with-roar/blob/master/spec/api/splines_endpoint_with_hyperclient_spec.rb). - -# Reference - -[Hyperclient API Reference](http://rubydoc.org/github/codegram/hyperclient/master/frames). - -# Hyperclient Users - -Using Hyperclient? Add your project to our wiki, please: . - -# Contributing - -Hyperclient is work of [many people](https://github.com/codegram/hyperclient/graphs/contributors). You're encouraged to submit [pull requests](https://github.com/codegram/hyperclient/pulls), [propose features and discuss issues](https://github.com/codegram/hyperclient/issues). See [CONTRIBUTING](CONTRIBUTING.md) for details. - -# License - -MIT License, see [LICENSE](LICENSE) for details. - -Copyright (c) 2012-2018 Oriol Gual, [Codegram Technologies](http://codegram.com) and Contributors diff --git a/RELEASING.md b/RELEASING.md deleted file mode 100644 index df10632..0000000 --- a/RELEASING.md +++ /dev/null @@ -1,69 +0,0 @@ -Releasing Hyperclient -===================== - -There're no particular rules about when to release hyperclient. Release bug fixes frequenty, features not so frequently and breaking API changes rarely. - -### Release - -Run tests, check that all tests succeed locally. - -``` -bundle install -rake -``` - -Check that the last build succeeded in CI for all supported platforms. - -Increment the version, modify [lib/hyperclient/version.rb](lib/hyperclient/version.rb). - -* Increment the third number if the release has bug fixes and/or very minor features, only (eg. change `0.5.1` to `0.5.2`). -* Increment the second number if the release contains major features or breaking API changes (eg. change `0.5.1` to `0.4.0`). - -Change "Next Release" in [CHANGELOG.md](CHANGELOG.md) to the new version. - -``` -0.4.0 (2014-01-27) -================== -``` - -Remove the line with "Your contribution here.", since there will be no more contributions to this release. - -Commit your changes. - -``` -git add CHANGELOG.md lib/hyperclient/version.rb -git commit -m "Preparing for release, 0.4.0." -git push origin master -``` - -Release. - -``` -$ rake release - -hyperclient 0.4.0 built to pkg/hyperclient-0.4.0.gem. -Tagged v0.4.0. -Pushed git commits and tags. -Pushed hyperclient 0.4.0 to rubygems.org. -``` - -### Prepare for the Next Version - -Add the next release to [CHANGELOG.md](CHANGELOG.md). - -``` -Next Release -============ - -* Your contribution here. -``` - -Increment the minor version, modify [lib/hyperclient/version.rb](lib/hyperclient/version.rb). - -Comit your changes. - -``` -git add CHANGELOG.md lib/hyperclient/version.rb -git commit -m "Preparing for next release, 0.4.1." -git push origin master -``` diff --git a/Rakefile b/Rakefile deleted file mode 100755 index fd517a6..0000000 --- a/Rakefile +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env rake - -require 'rubygems' -require 'bundler' -Bundler.setup :default, :test, :development - -Bundler::GemHelper.install_tasks - -require 'rake/testtask' - -Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.libs << 'test' - t.pattern = 'test/**/*_test.rb' - t.verbose = false - t.warning = true -end - -desc 'runs the whole spinach suite' -task :spinach do - ruby '-S spinach' -end - -require 'rubocop/rake_task' -RuboCop::RakeTask.new(:rubocop) - -task default: %i[test spinach rubocop] diff --git a/UPGRADING.md b/UPGRADING.md deleted file mode 100644 index a5611e1..0000000 --- a/UPGRADING.md +++ /dev/null @@ -1,149 +0,0 @@ -Upgrading Hyperclient -===================== - -### Upgrading to >= 0.9.0 - -Previous versions of Hyperclient performed asynchronous requests using [futuroscope](https://github.com/codegram/futuroscope) by default, which could be disabled by providing the `:async` option to each Hyperclient instance. This has been removed and you can remove any such code. - -```ruby -api = Hyperclient.new('https://grape-with-roar.herokuapp.com/api') do |client| - client.options[:async] = false -end -``` - -The default new behavior is synchronous. We recommend [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) for your asynchronous needs. - -Include [futuroscope](https://github.com/codegram/futuroscope) in your `Gemfile` and wrap Hyperclient requests into `Futuroscope::Future.new` blocks to get the old behavior. - -See [#133](https://github.com/codegram/hyperclient/pull/133) and [#123](https://github.com/codegram/hyperclient/issues/123) for more information. - -### Upgrading to >= 0.8.0 - -### Changes in curies - -Previous versions of Hyperclient misinterpreted the HAL RFC and used curies as if they were designed to enable compact URIs. Curies in HAL are provided for documentation purposes. - -Since version 0.8.0 they are no longer expanded. Consider the following example. - -```json -{ - "_links": { - "self": { "href": "/orders" }, - "curies": [{ - "name": "acme", - "href": "http://docs.acme.com/relations/%7Brel%7D", - "templated": true - }], - "acme:widgets": { "href": "/widgets" } - } -} -``` - -```rb -client = Hyperclient.new('http://example.org/api') - -client._links["acme:widgets"] - -# before -# => "http://docs.acme.com/relations/widgets" - -# after -# => "http://example.org/api/widgets" -``` - -In addition, you can now access and expand the curie. - -```rb -curie = client._links._curies["acme"] -curie.expand('widgets') -# => "http://docs.acme.com/relations/widgets" -``` - -See [#97](https://github.com/codegram/hyperclient/issues/97) for more information. - -### Upgrading to >= 0.7.0 - -#### Changes in default headers - -The default `Content-Type` is now `application/hal+json` and the `Accept` header includes `application/hal+json` as well. -If your API only accepts `application/json` as Content-Type, you can override the default headers in the client initialization block as such: - -```rb -Hyperclient.new('https://api.example.org/') do |client| - client.headers = { 'Content-Type' => 'application/json', 'Accept' => 'application/json,application/hal+json' } -end -``` - -#### Changes to default headers may impact Hyperclient in test - -If you are using Hyperclient to test an API as [described in README.md](https://github.com/codegram/hyperclient#testing-using-hyperclient) and if the API expects 'application/hal+json' as the content_type for requests, you may need to update how you set up Hyperclient in your specs. [As defined in the ```default_faraday_block``` method in ```Hyperclient::EntryPoint```](https://github.com/codegram/hyperclient/blob/9f908854395523b38e0d4fc834d6db1f8b6dfb22/lib/hyperclient/entry_point.rb#L129), you can specify that you are encoding requests via faraday as ```:hal_json```. - -```ruby -Hyperclient.new('http://example.org/api') do |client| - client.connection(default: false) do |conn| - conn.request :hal_json - conn.response :json - conn.use Faraday::Adapter::Rack, app - end -end -``` - -instead of: - -```ruby -Hyperclient.new('http://example.org/api') do |client| - client.connection(default: false) do |conn| - conn.request :json - conn.response :json - conn.use Faraday::Adapter::Rack, app - end -end -``` - -### Upgrading to >= 0.6.0 - -#### Changes in HTTP Error Handling - -The default Faraday block now uses `Faraday::Response::RaiseError` and will cause HTTP errors to be raised as exceptions. Older versions of Hyperclient swallowed the error and returned an empty resource. If you relied on checking for an HTTP response `status`, rescue `Faraday::ClientError`. - -#### Changes in Values Returned from HTTP Methods - -The `Link#_get` method has been aliased to `_resource`. All HTTP methods, including `_post`, `_put`, `_delete`, `_patch`, `_options` and `_head` now return instances of Resource. Older versions returned a `Faraday::Response`. - -#### Changes in URI Template Expansion - -A `MissingURITemplateVariablesException` exception will no longer be raised when expanding a link with no arguments. The `Link#_expand` method will now also accept zero arguments and default the variables to `{}`. This enables support for templated links with all optional arguments. - -### Upgrading to >= 0.5.0 - -#### Remove Navigational Elements - -You can, but no longer need to invoke `links`, `embedded`, `expand`, `attributes` or `resource` in most cases. Simply remove them. Navigational structures like `key.embedded.key` can also be collapsed. - -Here're a few examples: - -Instead Of | Write This ------------------------------------------------ | ----------------------- -`api.links.widgets` | `api.widgets` -`api.links.widgets.embedded.widgets.first` | `api.widgets.first` -`api.links.widgets.embedded.comments` | `api.widgets.comments` -`api.links.widget.expand(id: 3)` | `api.widget(id: 3)` -`api.links.widget.expand(id: 3).resource.id` | `api.widget(id: 3).id` - -If you prefer to specify the complete HAL navigational structure, you must rename the methods to their new underscore equivalents. See below. - -#### Change Naviational Elements and HTTP Verbs to Underscore Versions - -Navigational methods and HTTP verbs have been renamed to their underscore versions and are otherwise treated as attributes. - -Instead Of | Write This -------------------------------------------------------- | ---------------------------------------------------------------- -`api.links` | `api._links` -`api.links.widgets.embedded.widgets.first` | `api._links.widgets._embedded.first` -`api.links.widget.expand(id: 3).resource` | `api._links.widget._expand(id: 3)._resource` -`api.get` | `api._get` -`api.links.widgets.widget(id: 3).delete` | `api._links.widget._expand(id: 3)._delete` -`api.links.widgets.post(name: 'a widget')` | `api._links.widgets._post(name: 'a widget') -`api.links.widget.expand(id: 3).put(name: 'updated`) | `api._links.widget._expand(id: 3)._put(name: 'updated')` - -For more information see [#63](https://github.com/codegram/hyperclient/pull/63). diff --git a/examples/splines_api.rb b/examples/splines_api.rb deleted file mode 100644 index 61d959d..0000000 --- a/examples/splines_api.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'hyperclient' - -puts "Using Hyperclient #{Hyperclient::VERSION} ..." - -# create a new client -api = Hyperclient.new('https://grape-with-roar.herokuapp.com/api') - -# enumerate splines -api.splines.each do |spline| - puts spline.uuid - puts " reticulated: #{spline.reticulated ? 'yes' : 'no'}" - puts " thumbnail: #{spline['images:thumbnail']}" -end - -api._links.splines._embedded.splines.each do |_spline| - # ... equivalent to the above -end - -puts '*' * 10 - -# retrieve an existing spline -spline = api.spline(uuid: 123) -puts "Spline #{spline.uuid} is #{spline.reticulated ? 'reticulated' : 'not reticulated'}." - -# puts api._links.spline._expand(uuid: 'uuid')._resource._attributes.reticulated - -# spline.to_h - -# create a new spline -spline = api.splines._post(spline: { reticulated: true }) -puts "Created a #{spline.reticulated ? 'reticulated' : 'unreticulated'} spline #{spline.uuid}." - -# update an existing spline -spline = api.spline(uuid: 123)._put(spline: { reticulated: true }) -puts "Updated spline #{spline.uuid}, now #{spline.reticulated ? 'reticulated' : 'not reticulated'}." - -# delete an existing spline -spline = api.spline(uuid: 123)._delete -puts "Deleted spline #{spline.uuid}." diff --git a/features/api_navigation.feature b/features/api_navigation.feature deleted file mode 100644 index af7408f..0000000 --- a/features/api_navigation.feature +++ /dev/null @@ -1,43 +0,0 @@ -Feature: API navigation - In order to get the data from my API - As a user - I want to navigate through the API - - Scenario: Links - When I connect to the API - Then I should be able to navigate to posts and authors - - Scenario: Links - When I connect to the API - Then I should be able to paginate posts - Then I should be able to paginate authors - - Scenario: Templated links - Given I connect to the API - When I search for a post with a templated link - Then the API should receive the request with all the params - - Scenario: Templated links with multiple values - Given I connect to the API - When I search for posts by tag with a templated link - Then the API should receive the request for posts by tag with all the params - - Scenario: Attributes - Given I connect to the API - When I load a single post - Then I should be able to access it's title and body - - Scenario: Embedded resources - Given I connect to the API - When I load a single post - Then I should also be able to access it's embedded comments - - Scenario: Navigation links - When I connect to the API - Then I should be able to navigate to next page - Then I should be able to navigate to next page without links - - Scenario: Counts - When I connect to the API - Then I should be able to count embedded items - Then I should be able to iterate over embedded items diff --git a/features/default_config.feature b/features/default_config.feature deleted file mode 100644 index 0ea2e76..0000000 --- a/features/default_config.feature +++ /dev/null @@ -1,19 +0,0 @@ -Feature: Default config - In order to use HAL JSON apis - As a user - I want to make sure the default config is working - - Scenario: JSON headers - Given I use the default hyperclient config - When I connect to the API - Then the request should have been sent with the correct JSON headers - - Scenario: Send JSON data - Given I use the default hyperclient config - When I send some data to the API - Then it should have been encoded as JSON - - Scenario: Parse JSON data - Given I use the default hyperclient config - When I get some data from the API - Then it should have been parsed as JSON diff --git a/features/steps/api_navigation.rb b/features/steps/api_navigation.rb deleted file mode 100644 index b75b075..0000000 --- a/features/steps/api_navigation.rb +++ /dev/null @@ -1,77 +0,0 @@ -class Spinach::Features::ApiNavigation < Spinach::FeatureSteps - include API - - step 'I should be able to navigate to posts and authors' do - api._links.posts._resource - api._links['api:authors']._resource - - assert_requested :get, 'http://api.example.org/posts' - assert_requested :get, 'http://api.example.org/authors' - end - - step 'I should be able to paginate posts' do - assert_kind_of Enumerator, api.posts.each - assert_equal 4, api.posts.to_a.count - assert_requested :get, 'http://api.example.org/posts' - assert_requested :get, 'http://api.example.org/posts?page=2' - assert_requested :get, 'http://api.example.org/posts?page=3' - end - - step 'I should be able to paginate authors' do - assert_equal 1, api._links['api:authors'].to_a.count - assert_requested :get, 'http://api.example.org/authors' - end - - step 'I search for a post with a templated link' do - api._links.search._expand(q: 'something')._resource - end - - step 'the API should receive the request with all the params' do - assert_requested :get, 'http://api.example.org/search?q=something' - end - - step 'I search for posts by tag with a templated link' do - api._links.tagged._expand(tags: %w[foo bar])._resource - end - - step 'the API should receive the request for posts by tag with all the params' do - assert_requested :get, 'http://api.example.org/search?tags=foo&tags=bar' - end - - step 'I load a single post' do - @post = api._links.posts._links.last_post - end - - step 'I should be able to access it\'s title and body' do - assert_kind_of String, @post._attributes.title - assert_kind_of String, @post._attributes.body - end - - step 'I should also be able to access it\'s embedded comments' do - comment = @post._embedded.comments.first - assert_kind_of String, comment._attributes.title - end - - step 'I should be able to navigate to next page' do - assert_equal '/posts_of_page2', api._links.next._links.posts._url - end - - step 'I should be able to navigate to next page without links' do - assert_equal '/posts_of_page2', api.next.posts._url - end - - step 'I should be able to count embedded items' do - assert_equal 2, api._links.posts._resource._embedded.posts.count - assert_equal 2, api.posts._embedded.posts.count - assert_equal 4, api.posts.count - assert_equal 4, api.posts.map.count - end - - step 'I should be able to iterate over embedded items' do - count = 0 - api.posts.each do |_post| - count += 1 - end - assert_equal 4, count - end -end diff --git a/features/steps/default_config.rb b/features/steps/default_config.rb deleted file mode 100644 index 3d21c9e..0000000 --- a/features/steps/default_config.rb +++ /dev/null @@ -1,31 +0,0 @@ -class Spinach::Features::DefaultConfig < Spinach::FeatureSteps - include API - - step 'I use the default hyperclient config' do - @api = Hyperclient.new('http://api.example.org/') - end - - step 'the request should have been sent with the correct JSON headers' do - assert_requested :get, 'api.example.org', headers: { - 'Content-Type' => 'application/hal+json', 'Accept' => 'application/hal+json,application/json' - } - end - - step 'I send some data to the API' do - stub_request(:post, 'http://api.example.org/posts').to_return(headers: { 'Content-Type' => 'application/hal+json' }) - assert_equal 200, api._links.posts._post(title: 'My first blog post')._response.status - end - - step 'it should have been encoded as JSON' do - assert_requested :post, 'api.example.org/posts', body: '{"title":"My first blog post"}' - end - - step 'I get some data from the API' do - @posts = api._links.posts - end - - step 'it should have been parsed as JSON' do - assert_equal 4, @posts._attributes.total_posts.to_i - assert_equal 4, @posts._attributes['total_posts'].to_i - end -end diff --git a/features/support/api.rb b/features/support/api.rb deleted file mode 100644 index 61287c9..0000000 --- a/features/support/api.rb +++ /dev/null @@ -1,45 +0,0 @@ -require_relative 'fixtures' -module API - include Spinach::DSL - include WebMock::API - include Spinach::Fixtures - - before do - WebMock::Config.instance.query_values_notation = :flat_array - - stub_request(:any, /api.example.org*/).to_return(body: root_response, - headers: { 'Content-Type' => 'application/hal+json' }) - stub_request(:get, 'api.example.org').to_return(body: root_response, - headers: { 'Content-Type' => 'application/hal+json' }) - stub_request(:get, 'api.example.org/authors').to_return(body: authors_response, - headers: { 'Content-Type' => 'application/hal+json' }) - stub_request(:get, 'api.example.org/posts').to_return(body: posts_response, - headers: { 'Content-Type' => 'application/hal+json' }) - stub_request(:get, 'api.example.org/posts?page=2').to_return(body: posts_page2_response, - headers: { 'Content-Type' => 'application/hal+json' }) - stub_request(:get, 'api.example.org/posts?page=3').to_return(body: posts_page3_response, - headers: { 'Content-Type' => 'application/hal+json' }) - stub_request(:get, 'api.example.org/posts/1').to_return(body: post1_response, - headers: { 'Content-Type' => 'application/hal+json' }) - stub_request(:get, 'api.example.org/posts/2').to_return(body: post2_response, - headers: { 'Content-Type' => 'application/hal+json' }) - stub_request(:get, 'api.example.org/posts/3').to_return(body: post3_response, - headers: { 'Content-Type' => 'application/hal+json' }) - stub_request(:get, 'api.example.org/page2').to_return(body: page2_response, - headers: { 'Content-Type' => 'application/hal+json' }) - stub_request(:get, 'api.example.org/page3').to_return(body: page3_response, - headers: { 'Content-Type' => 'application/hal+json' }) - end - - def api - @api ||= Hyperclient.new('http://api.example.org/') - end - - step 'I connect to the API' do - api._links - end - - after do - WebMock.reset! - end -end diff --git a/features/support/env.rb b/features/support/env.rb deleted file mode 100644 index 918de40..0000000 --- a/features/support/env.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'minitest' -require 'webmock' -WebMock.enable! -require 'hyperclient' - -Minitest::Spec.new nil if defined?(Minitest::Spec) -Spinach.config[:failure_exceptions] << Minitest::Assertion - -class Spinach::FeatureSteps - include Minitest::Assertions - - attr_accessor :assertions - - def initialize(*args) - super - self.assertions = 0 - end -end diff --git a/features/support/fixtures.rb b/features/support/fixtures.rb deleted file mode 100644 index 66ef396..0000000 --- a/features/support/fixtures.rb +++ /dev/null @@ -1,177 +0,0 @@ -require 'json' - -module Spinach - module Fixtures - def root_response - '{ - "_links": { - "self": { "href": "/" }, - "posts": { "href": "/posts" }, - "search": { "href": "/search{?q}", "templated": true }, - "tagged": { "href": "/search{?tags*}", "templated": true }, - "api:authors": { "href": "/authors" }, - "next": { "href": "/page2" } - } - }' - end - - def authors_response - '{ - "_links": { - "self": { "href": "/authors" } - }, - "_embedded": { - "api:authors": [ - { - "name": "Lorem Ipsum", - "_links": { - "self": { "href": "/authors/1" } - } - } - ] - } - }' - end - - def posts_response - '{ - "_links": { - "self": { "href": "/posts" }, - "next": {"href": "/posts?page=2"}, - "last_post": {"href": "/posts/1"} - }, - "total_posts": "4", - "_embedded": { - "posts": [ - { - "title": "My first blog post", - "body": "Lorem ipsum dolor sit amet", - "_links": { - "self": { "href": "/posts/1" } - } - }, - { - "title": "My second blog post", - "body": "Lorem ipsum dolor sit amet", - "_links": { - "self": { "href": "/posts/2" } - } - } - ] - } - }' - end - - def posts_page2_response - '{ - "_links": { - "self": { "href": "/posts?page=2" }, - "next": { "href": "/posts?page=3" } - }, - "total_posts": "4", - "_embedded": { - "posts": [ - { - "title": "My third blog post", - "body": "Lorem ipsum dolor sit amet", - "_links": { - "self": { "href": "/posts/3" } - } - } - ] - } - }' - end - - def posts_page3_response - '{ - "_links": { - "self": { "href": "/posts?page=3" } - }, - "total_posts": "4", - "_embedded": { - "posts": [ - { - "title": "My third blog post", - "body": "Lorem ipsum dolor sit amet", - "_links": { - "self": { "href": "/posts/4" } - } - } - ] - } - }' - end - - def post1_response - '{ - "_links": { - "self": { "href": "/posts/1" } - }, - "title": "My first blog post", - "body": "Lorem ipsum dolor sit amet", - "_embedded": { - "comments": [ - { - "title": "Some comment" - } - ] - } - }' - end - - def post2_response - '{ - "_links": { - "self": { "href": "/posts/2" } - }, - "title": "My first blog post", - "body": "Lorem ipsum dolor sit amet", - "_embedded": { - "comments": [ - { - "title": "Some comment" - } - ] - } - }' - end - - def post3_response - '{ - "_links": { - "self": { "href": "/posts/3" } - }, - "title": "My first blog post", - "body": "Lorem ipsum dolor sit amet", - "_embedded": { - "comments": [ - { - "title": "Some comment" - } - ] - } - }' - end - - def page2_response - '{ - "_links": { - "self": { "href": "/page2" }, - "posts": { "href": "/posts_of_page2" }, - "next": { "href": "/page3" } - } - }' - end - - def page3_response - '{ - "_links": { - "self": { "href": "/page3" }, - "posts": { "href": "/posts_of_page3" }, - "api:authors": { "href": "/authors" } - } - }' - end - end -end diff --git a/hyperclient.gemspec b/hyperclient.gemspec deleted file mode 100644 index 64a978d..0000000 --- a/hyperclient.gemspec +++ /dev/null @@ -1,20 +0,0 @@ -require File.expand_path('lib/hyperclient/version', __dir__) - -Gem::Specification.new do |gem| - gem.authors = ['Oriol Gual'] - gem.email = ['oriol.gual@gmail.com'] - gem.description = 'Hyperclient is a Ruby Hypermedia API client.' - gem.summary = '' - gem.homepage = 'https://github.com/codegram/hyperclient/' - gem.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } - gem.files = `git ls-files`.split("\n") - gem.name = 'hyperclient' - gem.require_paths = ['lib'] - gem.version = Hyperclient::VERSION - - gem.add_dependency 'addressable' - gem.add_dependency 'faraday', '>= 2' - gem.add_dependency 'faraday-follow_redirects' - gem.add_dependency 'faraday_hal_middleware', '>= 0.2' - gem.metadata['rubygems_mfa_required'] = 'true' -end diff --git a/index.html b/index.html new file mode 100644 index 0000000..3c1d3b3 --- /dev/null +++ b/index.html @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/lib/hyperclient.rb b/lib/hyperclient.rb deleted file mode 100644 index 68e83f3..0000000 --- a/lib/hyperclient.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'hyperclient/collection' -require 'hyperclient/link' -require 'hyperclient/attributes' -require 'hyperclient/curie' -require 'hyperclient/entry_point' -require 'hyperclient/link_collection' -require 'hyperclient/resource' -require 'hyperclient/resource_collection' -require 'hyperclient/version' - -# Public: Hyperclient namespace. -# -module Hyperclient - # Public: Convenience method to create new EntryPoints. - # - # url - A String with the url of the API. - # - # Returns a Hyperclient::EntryPoint - def self.new(url, &block) - Hyperclient::EntryPoint.new(url, &block) - end -end diff --git a/lib/hyperclient/attributes.rb b/lib/hyperclient/attributes.rb deleted file mode 100644 index 54b9473..0000000 --- a/lib/hyperclient/attributes.rb +++ /dev/null @@ -1,24 +0,0 @@ -module Hyperclient - # Public: A wrapper class to easily acces the attributes in a Resource. - # - # Examples - # - # resource.attributes['title'] - # resource.attributes.title - # - class Attributes < Collection - RESERVED_PROPERTIES = [/^_links$/, /^_embedded$/].freeze # http://tools.ietf.org/html/draft-kelly-json-hal#section-4.1 - - # Public: Initializes the Attributes of a Resource. - # - # representation - The hash with the HAL representation of the Resource. - # - def initialize(representation) - @collection = if representation.is_a?(Hash) - representation.delete_if { |key, _value| RESERVED_PROPERTIES.any? { |p| p.match(key) } } - else - representation - end - end - end -end diff --git a/lib/hyperclient/collection.rb b/lib/hyperclient/collection.rb deleted file mode 100644 index 5bac533..0000000 --- a/lib/hyperclient/collection.rb +++ /dev/null @@ -1,89 +0,0 @@ -module Hyperclient - # Public: A helper class to wrap a collection of elements and provide - # Hash-like access or via a method call. - # - # Examples - # - # collection['value'] - # collection.value - # - class Collection - include Enumerable - - # Public: Initializes the Collection. - # - # collection - The Hash to be wrapped. - def initialize(collection) - @collection = collection - end - - # Public: Each implementation to allow the class to use the Enumerable - # benefits. - # - # Returns an Enumerator. - def each(&block) - @collection.each(&block) - end - - # Public: Checks if this collection includes a given key. - # - # key - A String or Symbol to check for existance. - # - # Returns True/False. - def include?(key) - @collection.include?(key) - end - - # Public: Returns a value from the collection for the given key. - # If the key can't be found, there are several options: - # With no other arguments, it will raise an KeyError exception; - # if default is given, then that will be returned; - # - # key - A String or Symbol of the value to get from the collection. - # default - An optional value to be returned if the key is not found. - # - # Returns an Object. - def fetch(*args) - @collection.fetch(*args) - end - - # Public: Provides Hash-like access to the collection. - # - # name - A String or Symbol of the value to get from the collection. - # - # Returns an Object. - def [](name) - @collection[name.to_s] - end - - # Public: Returns the wrapped collection as a Hash. - # - # Returns a Hash. - def to_h - @collection.to_hash - end - alias to_hash to_h - - def to_s - to_hash - end - - # Public: Provides method access to the collection values. - # - # It allows accessing a value as `collection.name` instead of - # `collection['name']` - # - # Returns an Object. - def method_missing(method_name, *_args, &_block) - @collection.fetch(method_name.to_s) do - raise "Could not find `#{method_name}` in #{self.class.name}" - end - end - - # Internal: Accessory method to allow the collection respond to the - # methods that will hit method_missing. - def respond_to_missing?(method_name, _include_private = false) - @collection.include?(method_name.to_s) - end - end -end diff --git a/lib/hyperclient/curie.rb b/lib/hyperclient/curie.rb deleted file mode 100644 index fa39521..0000000 --- a/lib/hyperclient/curie.rb +++ /dev/null @@ -1,48 +0,0 @@ -module Hyperclient - # Internal: Curies are named tokens that you can define in the document and use - # to express curie relation URIs in a friendlier, more compact fashion. - # - class Curie - # Public: Initializes a new Curie. - # - # curie_hash - The String with the URI of the curie. - # entry_point - The EntryPoint object to inject the configuration. - def initialize(curie_hash, entry_point) - @curie_hash = curie_hash - @entry_point = entry_point - end - - # Public: Indicates if the curie is an URITemplate or a regular URI. - # - # Returns true if it is templated. - # Returns false if it not templated. - def templated? - !!@curie_hash['templated'] - end - - # Public: Returns the name property of the Curie. - def name - @curie_hash['name'] - end - - # Public: Returns the href property of the Curie. - def href - @curie_hash['href'] - end - - def inspect - "#<#{self.class.name} #{@curie_hash}>" - end - - # Public: Expands the Curie when is templated with the given variables. - # - # rel - The String rel to expand. - # - # Returns a new expanded url. - def expand(rel) - return rel unless rel && templated? - - href&.gsub('{rel}', rel) - end - end -end diff --git a/lib/hyperclient/entry_point.rb b/lib/hyperclient/entry_point.rb deleted file mode 100644 index f9ce119..0000000 --- a/lib/hyperclient/entry_point.rb +++ /dev/null @@ -1,160 +0,0 @@ -require 'faraday/follow_redirects' -require 'faraday_hal_middleware' - -module Hyperclient - # Public: Exception that is raised when trying to modify an - # already initialized connection. - class ConnectionAlreadyInitializedError < StandardError - # Public: Returns a String with the exception message. - def message - 'The connection has already been initialized.' - end - end - - # Public: The EntryPoint is the main public API for Hyperclient. It is used to - # initialize an API client and setup the configuration. - # - # Examples - # - # client = Hyperclient::EntryPoint.new('http://my.api.org/') - # - # client = Hyperclient::EntryPoint.new('http://my.api.org/') do |entry_point| - # entry_point.connection do |conn| - # conn.use Faraday::Request::Instrumentation - # end - # entry_point.headers['Access-Token'] = 'token' - # end - # - class EntryPoint < Link - extend Forwardable - - # Public: Delegates common methods to be used with the Faraday connection. - def_delegators :connection, :params, :params= - - # Public: Initializes an EntryPoint. - # - # url - A String with the entry point of your API. - def initialize(url, &_block) - @link = { 'href' => url } - @entry_point = self - @options = {} - @connection = nil - @resource = nil - @key = nil - @uri_variables = nil - yield self if block_given? - end - - # Public: A Faraday connection to use as a HTTP client. - # - # options - A Hash containing additional options to pass to Farday. Use - # {default: false} if you want to skip using default Faraday options set by - # Hyperclient. - # - # Returns a Faraday::Connection. - def connection(options = {}, &block) - @faraday_options ||= options.dup - if block_given? - raise ConnectionAlreadyInitializedError if @connection - - @faraday_block = if @faraday_options.delete(:default) == false - block - else - lambda do |conn| - default_faraday_block.call(conn, &block) - end - end - else - @connection ||= Faraday.new(_url, faraday_options, &faraday_block) - end - end - - # Public: Headers included with every API request. - # - # Returns a Hash. - def headers - return @connection.headers if @connection - - @headers ||= default_headers - end - - # Public: Set headers. - # - # value - A Hash containing headers to include with every API request. - def headers=(value) - raise ConnectionAlreadyInitializedError if @connection - - @headers = value - end - - # Public: Options passed to Faraday - # - # Returns a Hash. - def faraday_options - (@faraday_options ||= {}).merge(headers: headers) - end - - # Public: Set Faraday connection options. - # - # value - A Hash containing options to pass to Faraday - def faraday_options=(value) - raise ConnectionAlreadyInitializedError if @connection - - @faraday_options = value - end - - # Public: Faraday block used with every API request. - # - # Returns a Proc. - def faraday_block - @faraday_block ||= default_faraday_block - end - - # Public: Set a Faraday block to use with every API request. - # - # value - A Proc accepting a Faraday::Connection. - def faraday_block=(value) - raise ConnectionAlreadyInitializedError if @connection - - @faraday_block = value - end - - # Public: Read/Set options. - # - # value - A Hash containing the client options. - attr_accessor :options - - private - - # Internal: Returns a block to initialize the Faraday connection. The - # default block includes a middleware to encode requests as JSON, a - # response middleware to parse JSON responses and sets the adapter as - # NetHttp. - # - # These middleware can always be changed by accessing the Faraday - # connection. - # - # Returns a block. - def default_faraday_block - lambda do |connection, &block| - connection.use Faraday::Response::RaiseError - connection.use Faraday::FollowRedirects::Middleware - connection.request :hal_json - connection.response :hal_json, content_type: /\bjson$/ - - block&.call(connection) - - connection.adapter :net_http - connection.options.params_encoder = Faraday::FlatParamsEncoder - end - end - - # Internal: Returns the default headers to initialize the Faraday connection. - # The default headers et the Content-Type and Accept to application/json. - # - # Returns a Hash. - def default_headers - { 'Content-Type' => 'application/hal+json', 'Accept' => 'application/hal+json,application/json' } - end - end -end diff --git a/lib/hyperclient/link.rb b/lib/hyperclient/link.rb deleted file mode 100644 index 7bb7113..0000000 --- a/lib/hyperclient/link.rb +++ /dev/null @@ -1,199 +0,0 @@ -require 'addressable' - -module Hyperclient - # Internal: The Link is used to let a Resource interact with the API. - # - class Link - include Enumerable - - # Public: Initializes a new Link. - # - # key - The key or name of the link. - # link - The String with the URI of the link. - # entry_point - The EntryPoint object to inject the configuration. - # uri_variables - The optional Hash with the variables to expand the link - # if it is templated. - def initialize(key, link, entry_point, uri_variables = nil) - @key = key - @link = link - @entry_point = entry_point - @uri_variables = uri_variables - @resource = nil - end - - # Public: Each implementation to allow the class to use the Enumerable - # benefits for paginated, embedded items. - # - # Returns an Enumerator. - def each(&block) - if block_given? - current = self - while current - coll = current.respond_to?(@key) ? current.send(@key) : _resource - coll.each(&block) - break unless current._links[:next] - - current = current._links.next - end - else - to_enum(:each) - end - end - - # Public: Indicates if the link is an URITemplate or a regular URI. - # - # Returns true if it is templated. - # Returns false if it not templated. - def _templated? - !!@link['templated'] - end - - # Public: Expands the Link when is templated with the given variables. - # - # uri_variables - The Hash with the variables to expand the URITemplate. - # - # Returns a new Link with the expanded variables. - def _expand(uri_variables = {}) - self.class.new(@key, @link, @entry_point, uri_variables) - end - - # Public: Returns the url of the Link. - def _url - return @link['href'] unless _templated? - - @url ||= _uri_template.expand(@uri_variables || {}).to_s - end - - # Public: Returns an array of variables from the URITemplate. - # - # Returns an empty array for regular URIs. - def _variables - _uri_template.variables - end - - # Public: Returns the type property of the Link - def _type - @link['type'] - end - - # Public: Returns the name property of the Link - def _name - @link['name'] - end - - # Public: Returns the deprecation property of the Link - def _deprecation - @link['deprecation'] - end - - # Public: Returns the profile property of the Link - def _profile - @link['profile'] - end - - # Public: Returns the title property of the Link - def _title - @link['title'] - end - - # Public: Returns the hreflang property of the Link - def _hreflang - @link['hreflang'] - end - - def _resource - @resource || _get - end - - # Public: Returns the Resource which the Link is pointing to. - def _get - http_method(:get) - end - - def _options - http_method(:options) - end - - def _head - http_method(:head) - end - - def _delete - http_method(:delete) - end - - def _post(params = {}) - http_method(:post, params) - end - - def _put(params = {}) - http_method(:put, params) - end - - def _patch(params = {}) - http_method(:patch, params) - end - - def inspect - "#<#{self.class.name}(#{@key}) #{@link}>" - end - - def to_s - _url - end - - private - - # Internal: Delegate the method further down the API if the resource cannot serve it. - def method_missing(method, *args, &block) - if _resource.respond_to?(method.to_s) - result = _resource.send(method, *args, &block) - result.nil? ? delegate_method(method, *args, &block) : result - else - super - end - end - - # Internal: Delegate the method to the API if the resource cannot serve it. - # - # This allows `api.posts` instead of `api._links.posts.embedded.posts` - def delegate_method(method, *args, &block) - return unless @key && _resource.respond_to?(@key) - - @delegate ||= _resource.send(@key) - return unless @delegate&.respond_to?(method.to_s) - - @delegate.send(method, *args, &block) - end - - # Internal: Accessory method to allow the link respond to the - # methods that will hit method_missing. - def respond_to_missing?(method, _include_private = false) - if @key && _resource.respond_to?(@key) && (delegate = _resource.send(@key)) && delegate.respond_to?(method.to_s) - true - else - _resource.respond_to?(method.to_s) - end - end - - # Internal: avoid delegating to resource - # - # #to_ary is called for implicit array coercion (such as parallel assignment - # or Array#flatten). Returning nil tells Ruby that this record is not Array-like. - def to_ary - nil - end - - # Internal: Memoization for a URITemplate instance - def _uri_template - @uri_template ||= Addressable::Template.new(@link['href']) - end - - def http_method(method, body = nil) - @resource = begin - response = @entry_point.connection.run_request(method, _url, body, nil) - Resource.new(response.body, @entry_point, response) - end - end - end -end diff --git a/lib/hyperclient/link_collection.rb b/lib/hyperclient/link_collection.rb deleted file mode 100644 index 004a47f..0000000 --- a/lib/hyperclient/link_collection.rb +++ /dev/null @@ -1,64 +0,0 @@ -module Hyperclient - # Public: A wrapper class to easily acces the links in a Resource. - # - # Examples - # - # resource.links['author'] - # resource.links.author - # - class LinkCollection < Collection - attr_reader :_curies - - # Public: Initializes a LinkCollection. - # - # collection - The Hash with the links. - # curies - The Hash with link curies. - # entry_point - The EntryPoint object to inject the configuration. - def initialize(collection, curies, entry_point) - if collection && !collection.respond_to?(:collect) - raise "Invalid response for LinkCollection. The response was: #{collection.inspect}" - end - - @_curies = (curies || {}).reduce({}) do |hash, curie_hash| - curie = build_curie(curie_hash, entry_point) - hash.update(curie.name => curie) - end - - @collection = (collection || {}).reduce({}) do |hash, (name, link)| - hash.update(name => build_link(name, link, @_curies, entry_point)) - end - end - - private - - # Internal: Creates links from the response hash. - # - # name - A String to identify the link's name. - # link_or_links - A Hash or an Array of hashes with the links to build. - # curies - An Array of Curies for templated links. - # entry_point - The EntryPoint object to inject the configuration. - # - # Returns a Link or an Array of Links when given an Array. - def build_link(name, link_or_links, curies, entry_point) - return unless link_or_links - - if link_or_links.respond_to?(:to_ary) - link_or_links.map do |link| - build_link(name, link, curies, entry_point) - end - else - Link.new(name, link_or_links, entry_point) - end - end - - # Internal: Creates a curie from the response hash. - # - # curie_hash - A Hash with the curie. - # entry_point - The EntryPoint object to inject the configuration. - # - # Returns a Link or an array of Links when given an Array. - def build_curie(curie_hash, entry_point) - Curie.new(curie_hash, entry_point) - end - end -end diff --git a/lib/hyperclient/resource.rb b/lib/hyperclient/resource.rb deleted file mode 100644 index d953e85..0000000 --- a/lib/hyperclient/resource.rb +++ /dev/null @@ -1,129 +0,0 @@ -require 'forwardable' - -module Hyperclient - # Public: Exception that is raised when passing in invalid representation data - # for the resource. - class InvalidRepresentationError < ArgumentError - attr_reader :representation - - def initialize(error_description, representation) - super(error_description) - @representation = representation - end - end - - # Public: Represents a resource from your API. Its responsability is to - # ease the way you access its attributes, links and embedded resources. - class Resource - extend Forwardable - - # Public: Returns the attributes of the Resource as Attributes. - attr_reader :_attributes - - # Public: Returns the links of the Resource as a LinkCollection. - attr_reader :_links - - # Public: Returns the embedded resource of the Resource as a - # ResourceCollection. - attr_reader :_embedded - - # Public: Returns the response object for the HTTP request that created this - # resource, if one exists. - attr_reader :_response - - # Public: Delegate all HTTP methods (get, post, put, delete, options and - # head) to its self link. - def_delegators :_self_link, :_get, :_post, :_put, :_delete, :_options, :_head - - # Public: Initializes a Resource. - # - # representation - The hash with the HAL representation of the Resource. - # entry_point - The EntryPoint object to inject the configutation. - def initialize(representation, entry_point, response = nil) - representation = validate(representation) - links = representation['_links'] || {} - - @_links = LinkCollection.new(links, links['curies'], entry_point) - @_embedded = ResourceCollection.new(representation['_embedded'], entry_point) - @_attributes = Attributes.new(representation) - @_entry_point = entry_point - @_response = response - end - - def inspect - "#<#{self.class.name} self_link:#{_self_link.inspect} attributes:#{@_attributes.inspect}>" - end - - def _success? - _response&.success? - end - - def _status - _response&.status - end - - def [](name) - send(name) if respond_to?(name) - end - - def fetch(key, *args) - return self[key] if respond_to?(key) - - if args.any? - args.first - elsif block_given? - yield key - else - raise KeyError - end - end - - private - - # Internal: Ensures the received representation is a valid Hash-lookalike. - def validate(representation) - return {} if representation.nil? || representation.empty? - - if representation.respond_to?(:to_hash) - representation.to_hash.dup - else - raise InvalidRepresentationError.new( - "Invalid representation for resource (got #{representation.class}, expected Hash). " \ - "Is your web server returning JSON HAL data with a 'Content-Type: application/hal+json' header?", - representation - ) - end - end - - # Internal: Returns the self Link of the Resource. Used to handle the HTTP - # methods. - def _self_link - @_links['self'] - end - - # Internal: Delegate the method to various elements of the resource. - # - # This allows `api.posts` instead of `api.links.posts.resource` - # as well as api.posts(id: 1) assuming posts is a link. - def method_missing(method, *args, &block) - if args.any? && args.first.is_a?(Hash) - _links.send(method, [], &block)._expand(*args) - elsif !Array.method_defined?(method) - %i[_attributes _embedded _links].each do |target| - target = send(target) - return target.send(method, *args, &block) if target.respond_to?(method.to_s) - end - super - end - end - - # Internal: Accessory method to allow the resource respond to - # methods that will hit method_missing. - def respond_to_missing?(method, include_private = false) - %i[_attributes _embedded _links].each do |target| - return true if send(target).respond_to?(method, include_private) - end - false - end - end -end diff --git a/lib/hyperclient/resource_collection.rb b/lib/hyperclient/resource_collection.rb deleted file mode 100644 index 915ed9c..0000000 --- a/lib/hyperclient/resource_collection.rb +++ /dev/null @@ -1,31 +0,0 @@ -module Hyperclient - # Public: A wrapper class to easily acces the embedded resources in a - # Resource. - # - # Examples - # - # resource.embedded['comments'] - # resource.embedded.comments - # - class ResourceCollection < Collection - # Public: Initializes a ResourceCollection. - # - # collection - The Hash with the embedded resources. - # entry_point - The EntryPoint object to inject the configuration. - # - def initialize(collection, entry_point) - @entry_point = entry_point - @collection = (collection || {}).reduce({}) do |hash, (name, resource)| - hash.update(name => build_resource(resource)) - end - end - - private - - def build_resource(representation) - return representation.map(&method(:build_resource)) if representation.is_a?(Array) - - Resource.new(representation, @entry_point) - end - end -end diff --git a/lib/hyperclient/version.rb b/lib/hyperclient/version.rb deleted file mode 100644 index 7b68657..0000000 --- a/lib/hyperclient/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module Hyperclient - VERSION = '2.0.1'.freeze -end diff --git a/test/fixtures/collection.json b/test/fixtures/collection.json deleted file mode 100644 index 626fea0..0000000 --- a/test/fixtures/collection.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "_links": { - "self": { "href": "..." }, - "filter": { "href": "...{?categories}" } // note: this is a URI template - }, - "categories": ["Microsoft", "Ruby", "Javascript", "Mobile"], - "_embedded": { - "videos": [{ - "_links": { - "self": { "href": "..." }, - "episodes": { "href": "..." } - }, - "title": "Real World ASP.NET MVC3", - "description": "In this advanced, somewhat-opinionated production you'll get your very own startup off the ground using ASP.NET MVC 3...", - "permitted": true - },{ - "_links": { - "self": { "href": "..." }, - "episodes": { "href": "..." } - }, - "title": "Example Video 2", - "description": "Lorem ipsum dolor sit amet, consectetur adipisicing elit", - "permitted": false - },{ - "_links": { - "self": { "href": "..." }, - "episodes": { "href": "..." } - }, - "title": "Example Video 3", - "description": "Lorem ipsum dolor sit amet, consectetur adipisicing elit", - "permitted": false - }] - } -} \ No newline at end of file diff --git a/test/fixtures/element.json b/test/fixtures/element.json deleted file mode 100644 index 7f83416..0000000 --- a/test/fixtures/element.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "_links": { - "self": { - "href": "/productions/1" - }, - "search": { - "href": "/productions/1?categories={search}", - "templated": true - }, - "gizmos": [ - { - "href": "/gizmos/1" - }, - { - "href": "/gizmos/2" - } - ], - "curies": [ - { - "name": "image", - "href": "/docs/images/{rel}", - "templated": true - } - ], - "null_link": null, - "image:thumbnail": { - "href": "/images/thumbnails/{version}.jpg", - "templated": true - } - }, - "title": "Real World ASP.NET MVC3", - "description": "In this advanced, somewhat-opinionated production you'll get your very own startup off the ground using ASP.NET MVC 3...", - "permitted": true, - "_hidden_attribute": "useful value", - "_embedded": { - "author": { - "_links": { - "self": { - "href": "/authors/1" - } - }, - "name": "Rob Conery" - }, - "episodes": [ - { - "_links": { - "self": { - "href": "/episodes/1" - }, - "media": { - "type": "video/webm; codecs='vp8.0, vorbis'", - "href": "/media/1" - } - }, - "title": "Foundations", - "description": "In this episode we talk about what it is we're doing: building our startup and getting ourselves off the ground. We take..", - "released": 1306972800 - }, - { - "_links": { - "self": { - "href": "/episodes/2" - }, - "media": { - "type": "video/ogg; codecs='theora, vorbis'", - "href": "/media/4" - } - }, - "title": "Membership", - "description": "In this episode Rob hooks up testing in an effort to deal with ASP.NET Membership. The team has decided..", - "released": 1306972800 - } - ] - } -} \ No newline at end of file diff --git a/test/fixtures/root.json b/test/fixtures/root.json deleted file mode 100644 index d0d26df..0000000 --- a/test/fixtures/root.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "_links": { - "self": { "href": "..." }, - "productions": { "href": "..." } - } -} \ No newline at end of file diff --git a/test/hyperclient/attributes_test.rb b/test/hyperclient/attributes_test.rb deleted file mode 100644 index 5c0587f..0000000 --- a/test/hyperclient/attributes_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require_relative '../test_helper' -require 'hyperclient' - -module Hyperclient - describe Attributes do - let(:representation) do - JSON.parse(File.read('test/fixtures/element.json')) - end - - let(:attributes) do - Attributes.new(representation) - end - - it 'does not set _links as an attribute' do - _(attributes).wont_respond_to :_links - end - - it 'does not set _embedded as an attribute' do - _(attributes).wont_respond_to :_embedded - end - - it 'sets normal attributes' do - _(attributes).must_respond_to :permitted - _(attributes.permitted).must_equal true - - _(attributes).must_respond_to :title - _(attributes.title).must_equal 'Real World ASP.NET MVC3' - end - - # Underscores should be allowed per http://tools.ietf.org/html/draft-kelly-json-hal#appendix-B.4 - it 'sets _hidden_attribute as an attribute' do - _(attributes).must_respond_to :_hidden_attribute - _(attributes._hidden_attribute).must_equal 'useful value' - end - - it 'is a collection' do - _(Attributes.ancestors).must_include Collection - end - end -end diff --git a/test/hyperclient/collection_test.rb b/test/hyperclient/collection_test.rb deleted file mode 100644 index 290956c..0000000 --- a/test/hyperclient/collection_test.rb +++ /dev/null @@ -1,74 +0,0 @@ -require_relative '../test_helper' -require 'hyperclient' - -module Hyperclient - describe Collection do - let(:representation) do - JSON.parse(File.read('test/fixtures/element.json')) - end - - let(:collection) do - Collection.new(representation) - end - - it 'exposes the collection as methods' do - _(collection.title).must_equal 'Real World ASP.NET MVC3' - _(collection.description).must_match(/production/) - _(collection.permitted).must_equal true - end - - it 'exposes collection as a hash' do - _(collection['title']).must_equal 'Real World ASP.NET MVC3' - _(collection['description']).must_match(/production/) - _(collection['permitted']).must_equal true - end - - it 'correctly responds to methods' do - _(collection).must_respond_to :title - end - - it 'acts as enumerable' do - names = collection.map do |name, _value| - name - end - - _(names).must_equal %w[_links title description permitted _hidden_attribute _embedded] - end - - describe '#to_hash' do - it 'returns the wrapped collection as a hash' do - _(collection.to_hash).must_be_kind_of Hash - end - end - - describe 'include?' do - it 'returns true for keys that exist' do - _(collection.include?('_links')).must_equal true - end - - it 'returns false for missing keys' do - _(collection.include?('missing key')).must_equal false - end - end - - describe '#fetch' do - it 'returns the value for keys that exist' do - _(collection.fetch('https://github.com/title')).must_equal 'Real World ASP.NET MVC3' - end - - it 'raises an error for missing keys' do - _(proc { collection.fetch('https://github.com/missing%20key') }).must_raise KeyError - end - - describe 'with a default value' do - it 'returns the value for keys that exist' do - _(collection.fetch('https://github.com/title', 'default')).must_equal 'Real World ASP.NET MVC3' - end - - it 'returns the default value for missing keys' do - _(collection.fetch('https://github.com/missing%20key', 'default')).must_equal 'default' - end - end - end - end -end diff --git a/test/hyperclient/curie_test.rb b/test/hyperclient/curie_test.rb deleted file mode 100644 index 0f7cfb4..0000000 --- a/test/hyperclient/curie_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -require_relative '../test_helper' -require 'hyperclient' - -module Hyperclient - describe Curie do - let(:entry_point) do - EntryPoint.new('http://api.example.org/') - end - - describe 'templated?' do - it 'returns true if the curie is templated' do - curie = Curie.new({ 'name' => 'image', 'templated' => true }, entry_point) - - _(curie.templated?).must_equal true - end - - it 'returns false if the curie is not templated' do - curie = Curie.new({ 'name' => 'image' }, entry_point) - - _(curie.templated?).must_equal false - end - end - - let(:curie) do - Curie.new({ 'name' => 'image', 'href' => '/images/{rel}', 'templated' => true }, entry_point) - end - describe '_name' do - it 'returns curie name' do - _(curie.name).must_equal 'image' - end - end - describe 'expand' do - it 'expands link' do - _(curie.expand('thumbnail')).must_equal '/images/thumbnail' - end - end - end -end diff --git a/test/hyperclient/entry_point_test.rb b/test/hyperclient/entry_point_test.rb deleted file mode 100644 index 8aa3a98..0000000 --- a/test/hyperclient/entry_point_test.rb +++ /dev/null @@ -1,185 +0,0 @@ -require_relative '../test_helper' -require 'hyperclient' - -module Hyperclient - describe EntryPoint do - describe 'default' do - let(:entry_point) do - EntryPoint.new 'http://my.api.org/' - end - - describe 'connection' do - it 'creates a Faraday connection with the entry point url' do - _(entry_point.connection.url_prefix.to_s).must_equal 'http://my.api.org/' - end - - it 'creates a Faraday connection with the default headers' do - _(entry_point.headers['Content-Type']).must_equal 'application/hal+json' - _(entry_point.headers['Accept']).must_equal 'application/hal+json,application/json' - end - - it 'can update headers after a connection has been constructed' do - _(entry_point.connection).must_be_kind_of Faraday::Connection - entry_point.headers.update('Content-Type' => 'application/foobar') - - _(entry_point.headers['Content-Type']).must_equal 'application/foobar' - end - - it 'can insert additional middleware after a connection has been constructed' do - _(entry_point.connection).must_be_kind_of Faraday::Connection - entry_point.connection.use Faraday::Request::Instrumentation - handlers = entry_point.connection.builder.handlers - - _(handlers).must_include Faraday::Request::Instrumentation - end - - it 'creates a Faraday connection with the default block' do - handlers = entry_point.connection.builder.handlers - - _(handlers).must_include Faraday::Response::RaiseError - _(handlers).must_include Faraday::FollowRedirects::Middleware - _(handlers).must_include Faraday::HalJson::Request - _(handlers).must_include Faraday::HalJson::Response - - _(entry_point.connection.options.params_encoder).must_equal Faraday::FlatParamsEncoder - end - - it 'raises a ConnectionAlreadyInitializedError if attempting to modify headers' do - _(entry_point.connection).must_be_kind_of Faraday::Connection - _(-> { entry_point.headers = {} }).must_raise ConnectionAlreadyInitializedError - end - - it 'raises a ConnectionAlreadyInitializedError if attempting to modify the faraday block' do - _(entry_point.connection).must_be_kind_of Faraday::Connection - _(-> { entry_point.connection {} }).must_raise ConnectionAlreadyInitializedError - end - end - - describe 'initialize' do - it 'sets a Link with the entry point url' do - _(entry_point._url).must_equal 'http://my.api.org/' - end - end - end - - describe 'faraday_options' do - let(:entry_point) do - EntryPoint.new 'http://my.api.org/' do |entry_point| - entry_point.faraday_options = { proxy: 'http://my.proxy:8080/' } - end - end - - describe 'connection' do - it 'creates a Faraday connection with the entry point url' do - _(entry_point.connection.url_prefix.to_s).must_equal 'http://my.api.org/' - end - - it 'creates a Faraday connection with the default headers' do - _(entry_point.headers['Content-Type']).must_equal 'application/hal+json' - _(entry_point.headers['Accept']).must_equal 'application/hal+json,application/json' - end - - it 'creates a Faraday connection with options' do - _(entry_point.connection.proxy).must_be_kind_of Faraday::ProxyOptions - _(entry_point.connection.proxy.uri.to_s).must_equal 'http://my.proxy:8080/' - end - end - end - - describe 'options' do - let(:entry_point) do - EntryPoint.new 'http://my.api.org/' do |entry_point| - entry_point.connection(proxy: 'http://my.proxy:8080/') - end - end - - describe 'connection' do - it 'creates a Faraday connection with the entry point url' do - _(entry_point.connection.url_prefix.to_s).must_equal 'http://my.api.org/' - end - - it 'creates a Faraday connection with the default headers' do - _(entry_point.headers['Content-Type']).must_equal 'application/hal+json' - _(entry_point.headers['Accept']).must_equal 'application/hal+json,application/json' - end - - it 'creates a Faraday connection with options' do - _(entry_point.connection.proxy).must_be_kind_of Faraday::ProxyOptions - _(entry_point.connection.proxy.uri.to_s).must_equal 'http://my.proxy:8080/' - end - end - end - - describe 'custom' do - let(:entry_point) do - EntryPoint.new 'http://my.api.org/' do |entry_point| - entry_point.connection(default: false) do |conn| - conn.request :json - conn.response :json, content_type: /\bjson$/ - conn.adapter :net_http - end - - entry_point.headers = { - 'Content-Type' => 'application/foobar', - 'Accept' => 'application/foobar' - } - end - end - - describe 'connection' do - it 'creates a Faraday connection with the entry point url' do - _(entry_point.connection.url_prefix.to_s).must_equal 'http://my.api.org/' - end - - it 'creates a Faraday connection with non-default headers' do - _(entry_point.headers['Content-Type']).must_equal 'application/foobar' - _(entry_point.headers['Accept']).must_equal 'application/foobar' - end - - it 'creates a Faraday connection with the default block' do - handlers = entry_point.connection.builder.handlers - - _(handlers).wont_include Faraday::Response::RaiseError - _(handlers).wont_include Faraday::FollowRedirects - _(handlers).must_include Faraday::Request::Json - _(handlers).must_include Faraday::Response::Json - end - end - end - - describe 'inherited' do - let(:entry_point) do - EntryPoint.new 'http://my.api.org/' do |entry_point| - entry_point.connection do |conn| - conn.use Faraday::Request::Instrumentation - end - entry_point.headers['Access-Token'] = 'token' - end - end - - describe 'connection' do - it 'creates a Faraday connection with the default and additional headers' do - _(entry_point.headers['Content-Type']).must_equal 'application/hal+json' - _(entry_point.headers['Accept']).must_equal 'application/hal+json,application/json' - _(entry_point.headers['Access-Token']).must_equal 'token' - end - - it 'creates a Faraday connection with the entry point url' do - _(entry_point.connection.url_prefix.to_s).must_equal 'http://my.api.org/' - end - - it 'creates a Faraday connection with the default block plus any additional handlers' do - handlers = entry_point.connection.builder.handlers - - _(handlers).must_include Faraday::Request::Instrumentation - _(handlers).must_include Faraday::Response::RaiseError - _(handlers).must_include Faraday::FollowRedirects::Middleware - _(handlers).must_include Faraday::HalJson::Request - _(handlers).must_include Faraday::HalJson::Response - - _(entry_point.connection.options.params_encoder).must_equal Faraday::FlatParamsEncoder - end - end - end - end -end diff --git a/test/hyperclient/link_collection_test.rb b/test/hyperclient/link_collection_test.rb deleted file mode 100644 index 7eb49a3..0000000 --- a/test/hyperclient/link_collection_test.rb +++ /dev/null @@ -1,83 +0,0 @@ -require_relative '../test_helper' -require 'hyperclient' - -module Hyperclient - describe LinkCollection do - let(:entry_point) { stub('Entry point', config: { base_uri: '/' }) } - - let(:representation) do - JSON.parse(File.read('test/fixtures/element.json')) - end - - let(:links) do - LinkCollection.new(representation['_links'], representation['_links']['curies'], entry_point) - end - - it 'is a collection' do - _(LinkCollection.ancestors).must_include Collection - end - - it 'initializes the collection with links' do - _(links).must_respond_to :search - _(links).must_respond_to :gizmos - end - - it 'returns link objects for each link' do - _(links.search).must_be_kind_of Link - _(links['self']).must_be_kind_of Link - - _(links.gizmos).must_be_kind_of Array - _(links['gizmos']).must_be_kind_of Array - end - - describe 'plain link' do - let(:plain_link) { links.self } - - it 'must be correct' do - _(plain_link._url).must_equal '/productions/1' - end - end - - describe 'templated link' do - let(:templated_link) { links.search } - - it 'must expand' do - _(templated_link._expand(search: 'gizmos')._url).must_equal '/productions/1?categories=gizmos' - end - end - - describe 'curied link collection' do - let(:curied_link) { links['image:thumbnail'] } - let(:curie) { links._curies['image'] } - - it 'must expand' do - _(curied_link._expand(version: 'small')._url).must_equal '/images/thumbnails/small.jpg' - end - it 'exposes curie' do - _(curie.expand('thumbnail')).must_equal '/docs/images/thumbnail' - end - end - - describe 'array of links' do - let(:gizmos) { links.gizmos } - - it 'should have 2 items' do - _(gizmos.length).must_equal 2 - end - - it 'must be an array of Links' do - gizmos.each do |link| - _(link).must_be_kind_of Link - end - end - end - - describe 'null link value' do - let(:null_link) { links.null_link } - - it 'must be nil' do - _(null_link).must_be_nil - end - end - end -end diff --git a/test/hyperclient/link_test.rb b/test/hyperclient/link_test.rb deleted file mode 100644 index f4400de..0000000 --- a/test/hyperclient/link_test.rb +++ /dev/null @@ -1,366 +0,0 @@ -require_relative '../test_helper' -require 'hyperclient' - -module Hyperclient - describe Link do - let(:entry_point) do - EntryPoint.new('http://api.example.org/') - end - - %w[type deprecation name profile title hreflang].each do |prop| - describe prop do - it 'returns the property value' do - link = Link.new('key', { prop => 'value' }, entry_point) - - _(link.send("_#{prop}")).must_equal 'value' - end - - it 'returns nil if the property is not present' do - link = Link.new('key', {}, entry_point) - - _(link.send("_#{prop}")).must_be_nil - end - end - end - - describe '_templated?' do - it 'returns true if the link is templated' do - link = Link.new('key', { 'templated' => true }, entry_point) - - _(link._templated?).must_equal true - end - - it 'returns false if the link is not templated' do - link = Link.new('key', {}, entry_point) - - _(link._templated?).must_equal false - end - end - - describe '_variables' do - it 'returns a list of required variables' do - link = Link.new('key', { 'href' => '/orders{?id,owner}', 'templated' => true }, entry_point) - - _(link._variables).must_equal %w[id owner] - end - - it 'returns an empty array for untemplated links' do - link = Link.new('key', { 'href' => '/orders' }, entry_point) - - _(link._variables).must_equal [] - end - end - - describe '_expand' do - describe 'required argument' do - it 'builds a Link with the templated URI representation' do - link = Link.new('key', { 'href' => '/orders/{id}', 'templated' => true }, entry_point) - - _(link._expand(id: '1')._url).must_equal '/orders/1' - end - - it 'expands an uri template without variables' do - link = Link.new('key', { 'href' => '/orders/{id}', 'templated' => true }, entry_point) - - _(link._expand._url).must_equal '/orders/' - _(link._url).must_equal '/orders/' - end - end - - describe 'query string argument' do - it 'builds a Link with the templated URI representation' do - link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point) - - _(link._expand(id: '1')._url).must_equal '/orders?id=1' - end - - it 'expands an uri template without variables' do - link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point) - - _(link._expand._url).must_equal '/orders' - _(link._url).must_equal '/orders' - end - - it 'does not expand unknown variables' do - link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point) - - _(link._expand(unknown: '1')._url).must_equal '/orders' - end - - it 'only expands known variables' do - link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point) - - _(link._expand(unknown: '1', id: '2')._url).must_equal '/orders?id=2' - end - - it 'only expands templated links' do - link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => false }, entry_point) - - _(link._expand(id: '1')._url).must_equal '/orders{?id}' - end - end - end - - describe '_url' do - it 'expands an uri template without variables' do - link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point) - - _(link._url).must_equal '/orders' - end - - it 'expands an uri template with variables' do - link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point, id: 1) - - _(link._url).must_equal '/orders?id=1' - end - - it 'does not expand an uri template with unknown variables' do - link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point, unknown: 1) - - _(link._url).must_equal '/orders' - end - - it 'only expands known variables in a uri template' do - link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point, unknown: 1, id: 2) - - _(link._url).must_equal '/orders?id=2' - end - - it 'returns the link when no uri template' do - link = Link.new('key', { 'href' => '/orders' }, entry_point) - - _(link._url).must_equal '/orders' - end - - it 'aliases to_s to _url' do - link = Link.new('key', { 'href' => '/orders{?id}', 'templated' => true }, entry_point, id: 1) - - _(link.to_s).must_equal '/orders?id=1' - end - end - - describe '_resource' do - it 'builds a resource with the link href representation' do - Resource.expects(:new) - - link = Link.new('key', { 'href' => '/' }, entry_point) - - stub_request(entry_point.connection) do |stub| - stub.get('http://api.example.org/') { [200, {}, nil] } - end - - link._resource - end - end - - describe 'get' do - it 'sends a GET request with the link url' do - link = Link.new('key', { 'href' => '/productions/1' }, entry_point) - - stub_request(entry_point.connection) do |stub| - stub.get('http://api.example.org/productions/1') { [200, {}, nil] } - end - - _(link._get).must_be_kind_of Resource - end - - it 'raises exceptions by default' do - link = Link.new('key', { 'href' => '/productions/1' }, entry_point) - - stub_request(entry_point.connection) do |stub| - stub.get('http://api.example.org/productions/1') { [400, {}, nil] } - end - - _(-> { link._get }).must_raise Faraday::ClientError - end - end - - describe '_options' do - it 'sends a OPTIONS request with the link url' do - link = Link.new('key', { 'href' => '/productions/1' }, entry_point) - - stub_request(entry_point.connection) do |stub| - stub.options('http://api.example.org/productions/1') { [200, {}, nil] } - end - - _(link._options).must_be_kind_of Resource - end - end - - describe '_head' do - it 'sends a HEAD request with the link url' do - link = Link.new('key', { 'href' => '/productions/1' }, entry_point) - - stub_request(entry_point.connection) do |stub| - stub.head('http://api.example.org/productions/1') { [200, {}, nil] } - end - - _(link._head).must_be_kind_of Resource - end - end - - describe '_delete' do - it 'sends a DELETE request with the link url' do - link = Link.new('key', { 'href' => '/productions/1' }, entry_point) - - stub_request(entry_point.connection) do |stub| - stub.delete('http://api.example.org/productions/1') { [200, {}, nil] } - end - - _(link._delete).must_be_kind_of Resource - end - end - - describe '_post' do - let(:link) { Link.new('key', { 'href' => '/productions/1' }, entry_point) } - - it 'sends a POST request with the link url and params' do - stub_request(entry_point.connection) do |stub| - stub.post('http://api.example.org/productions/1') { [200, {}, nil] } - end - - _(link._post('foo' => 'bar')).must_be_kind_of Resource - end - - it 'defaults params to an empty hash' do - stub_request(entry_point.connection) do |stub| - stub.post('http://api.example.org/productions/1') { [200, {}, nil] } - end - - _(link._post).must_be_kind_of Resource - end - end - - describe '_put' do - let(:link) { Link.new('key', { 'href' => '/productions/1' }, entry_point) } - - it 'sends a PUT request with the link url and params' do - stub_request(entry_point.connection) do |stub| - stub.put('http://api.example.org/productions/1', '{"foo":"bar"}') { [200, {}, nil] } - end - - _(link._put('foo' => 'bar')).must_be_kind_of Resource - end - - it 'defaults params to an empty hash' do - stub_request(entry_point.connection) do |stub| - stub.put('http://api.example.org/productions/1') { [200, {}, nil] } - end - - _(link._put).must_be_kind_of Resource - end - end - - describe '_patch' do - let(:link) { Link.new('key', { 'href' => '/productions/1' }, entry_point) } - - it 'sends a PATCH request with the link url and params' do - stub_request(entry_point.connection) do |stub| - stub.patch('http://api.example.org/productions/1', '{"foo":"bar"}') { [200, {}, nil] } - end - - _(link._patch('foo' => 'bar')).must_be_kind_of Resource - end - - it 'defaults params to an empty hash' do - stub_request(entry_point.connection) do |stub| - stub.patch('http://api.example.org/productions/1') { [200, {}, nil] } - end - - _(link._patch).must_be_kind_of Resource - end - end - - describe 'inspect' do - it 'outputs a custom-friendly output' do - link = Link.new('key', { 'href' => '/productions/1' }, 'foo') - - _(link.inspect).must_include 'Link' - _(link.inspect).must_include({ 'href' => '/productions/1' }.inspect) - end - end - - describe 'method_missing' do - describe 'delegation' do - it 'delegates when link key matches' do - resource = Resource.new({ '_links' => { 'orders' => { 'href' => '/orders' } } }, entry_point) - - stub_request(entry_point.connection) do |stub| - stub.get('http://api.example.org/orders') { [200, {}, { '_embedded' => { 'orders' => [{ 'id' => 1 }] } }] } - end - - _(resource.orders._embedded.orders.first.id).must_equal 1 - _(resource.orders.first.id).must_equal 1 - end - - it 'can handle false values in the response' do - resource = Resource.new({ '_links' => { 'orders' => { 'href' => '/orders' } } }, entry_point) - - stub_request(entry_point.connection) do |stub| - stub.get('http://api.example.org/orders') { [200, {}, { 'any' => false }] } - end - - _(resource.orders.any).must_equal false - end - - it "doesn't delegate when link key doesn't match" do - resource = Resource.new({ '_links' => { 'foos' => { 'href' => '/orders' } } }, entry_point) - - stub_request(entry_point.connection) do |stub| - stub.get('http://api.example.org/orders') { [200, {}, { '_embedded' => { 'orders' => [{ 'id' => 1 }] } }] } - end - - _(resource.foos._embedded.orders.first.id).must_equal 1 - _(resource.foos.first).must_be_nil - end - - it 'backtracks when navigating links' do - resource = Resource.new({ '_links' => { 'next' => { 'href' => '/page2' } } }, entry_point) - - stub_request(entry_point.connection) do |stub| - stub.get('http://api.example.org/page2') { [200, {}, { '_links' => { 'next' => { 'href' => 'http://api.example.org/page3' } } }] } - end - - _(resource.next._links.next._url).must_equal 'http://api.example.org/page3' - end - end - - describe 'resource' do - before do - stub_request(entry_point.connection) do |stub| - stub.get('http://myapi.org/orders') { [200, {}, '{"resource": "This is the resource"}'] } - end - - Resource.stubs(:new).returns(resource) - end - - let(:resource) { mock('Resource') } - let(:link) { Link.new('orders', { 'href' => 'http://myapi.org/orders' }, entry_point) } - - it 'delegates unkown methods to the resource' do - Resource.expects(:new).returns(resource).at_least_once - resource.expects(:embedded) - - link.embedded - end - - it 'raises an error when the method does not exist in the resource' do - _(-> { link.this_method_does_not_exist }).must_raise NoMethodError - end - - it 'responds to missing methods' do - resource.expects(:respond_to?).with('orders').returns(false) - resource.expects(:respond_to?).with('embedded').returns(true) - - _(link.respond_to?(:embedded)).must_equal true - end - - it 'does not delegate to_ary to resource' do - resource.expects(:to_ary).never - - _([[link, link]].flatten).must_equal [link, link] - end - end - end - end -end diff --git a/test/hyperclient/resource_collection_test.rb b/test/hyperclient/resource_collection_test.rb deleted file mode 100644 index 44a1faa..0000000 --- a/test/hyperclient/resource_collection_test.rb +++ /dev/null @@ -1,34 +0,0 @@ -require_relative '../test_helper' -require 'hyperclient' - -module Hyperclient - describe ResourceCollection do - let(:entry_point) { stub('Entry point', config: { base_uri: '/' }) } - - let(:representation) do - JSON.parse(File.read('test/fixtures/element.json')) - end - - let(:resources) do - ResourceCollection.new(representation['_embedded'], entry_point) - end - - it 'is a collection' do - _(ResourceCollection.ancestors).must_include Collection - end - - it 'initializes the collection with resources' do - _(resources).must_respond_to :author - _(resources).must_respond_to :episodes - end - - it 'returns resource objects for each resource' do - _(resources.author).must_be_kind_of Resource - end - - it 'also builds arras of resource' do - _(resources.episodes).must_be_kind_of Array - _(resources.episodes.first).must_be_kind_of Resource - end - end -end diff --git a/test/hyperclient/resource_test.rb b/test/hyperclient/resource_test.rb deleted file mode 100644 index 6c96c12..0000000 --- a/test/hyperclient/resource_test.rb +++ /dev/null @@ -1,232 +0,0 @@ -require_relative '../test_helper' -require 'hyperclient' - -module Hyperclient - describe Resource do - let(:entry_point) { mock('Entry point') } - - describe 'initialize' do - it 'initializes its links' do - LinkCollection.expects(:new).with({ 'self' => { 'href' => '/orders/523' } }, nil, entry_point) - - Resource.new({ '_links' => { 'self' => { 'href' => '/orders/523' } } }, entry_point) - end - - it 'initializes its attributes' do - Attributes.expects(:new).with({ foo: :bar }) - - Resource.new({ foo: :bar }, entry_point) - end - - it 'initializes links' do - ResourceCollection.expects(:new).with({ 'orders' => [] }, entry_point) - - Resource.new({ '_embedded' => { 'orders' => [] } }, entry_point) - end - - it 'initializes the response' do - mock_response = mock(body: {}) - - resource = Resource.new(mock_response.body, entry_point, mock_response) - - _(resource._response).must_equal mock_response - end - - it 'does not mutate the response.body' do - body = { 'foo' => 'bar', '_links' => {}, '_embedded' => {} } - mock_response = stub(body: body.dup) - - resource = Resource.new(mock_response.body, entry_point, mock_response) - - _(resource._response.body).must_equal body - end - - describe 'with an empty body in response' do - it 'initializes the response' do - mock_response = mock(body: '') - - resource = Resource.new(mock_response.body, entry_point, mock_response) - - _(resource._response).must_equal mock_response - end - end - - describe 'with an invalid representation' do - it 'raises an InvalidRepresentationError' do - _(proc { Resource.new('invalid representation data', entry_point) }).must_raise InvalidRepresentationError - end - end - end - - describe '_links' do - it '_expand' do - resource = Resource.new({ '_links' => { 'orders' => { 'href' => '/orders/{id}', 'templated' => true } } }, - entry_point) - - _(resource._links.orders._expand(id: 1)._url).must_equal '/orders/1' - _(resource.orders._expand(id: 1)._url).must_equal '/orders/1' - _(resource.orders(id: 1)._url).must_equal '/orders/1' - end - end - - describe 'accessors' do - let(:resource) do - Resource.new({}, entry_point) - end - - describe 'links' do - it 'returns a LinkCollection' do - _(resource._links).must_be_kind_of LinkCollection - end - end - - describe 'attributes' do - it 'returns a Attributes' do - _(resource._attributes).must_be_kind_of Attributes - end - end - - describe 'embedded' do - it 'returns a ResourceCollection' do - _(resource._embedded).must_be_kind_of ResourceCollection - end - end - - describe 'method_missing' do - it 'delegates to attributes' do - resource._attributes.expects(:foo).returns('bar') - - _(resource.foo).must_equal 'bar' - end - - it 'delegates to links' do - resource._links.expects(:foo).returns('bar') - - _(resource.foo).must_equal 'bar' - end - - it 'delegates to embedded' do - resource._embedded.expects(:foo).returns('bar') - - _(resource.foo).must_equal 'bar' - end - - it 'delegates to attributes, links, embedded' do - resource._attributes.expects('respond_to?').with('foo').returns(false) - resource._links.expects('respond_to?').with('foo').returns(false) - resource._embedded.expects('respond_to?').with('foo').returns(false) - - _(-> { resource.foo }).must_raise NoMethodError - end - - it 'delegates []' do - resource._attributes.expects(:foo).returns('bar') - - _(resource['foo']).must_equal 'bar' - end - - describe '#fetch' do - it 'returns the value for keys that exist' do - resource._attributes.expects(:foo).returns('bar') - - _(resource.fetch('https://github.com/foo')).must_equal 'bar' - end - - it 'raises an error for missing keys' do - _(proc { resource.fetch('https://github.com/missing%20key') }).must_raise KeyError - end - - describe 'with a default value' do - it 'returns the value for keys that exist' do - resource._attributes.expects(:foo).returns('bar') - - _(resource.fetch('https://github.com/foo', 'default value')).must_equal 'bar' - end - - it 'returns the default value for missing keys' do - _(resource.fetch('https://github.com/missing%20key', 'default value')).must_equal 'default value' - end - end - - describe 'with a block' do - it 'returns the value for keys that exist' do - resource._attributes.expects(:foo).returns('bar') - - _(resource.fetch('https://github.com/foo') { 'default value' }).must_equal 'bar' - end - - it 'returns the value from the block' do - _(resource.fetch('https://github.com/z') { 'go fish!' }).must_equal 'go fish!' - end - - it 'returns the value with args from the block' do - _(resource.fetch('https://github.com/z') { |el| "go fish, #{el}" }).must_equal 'go fish, z' - end - end - end - end - end - - it 'uses its self Link to handle HTTP connections' do - self_link = mock('Self Link') - self_link.expects(:_get) - - LinkCollection.expects(:new).returns('self' => self_link) - resource = Resource.new({}, entry_point) - - resource._get - end - - describe '._success?' do - describe 'with a response object' do - let(:resource) do - Resource.new({}, entry_point, mock_response) - end - - let(:mock_response) do - mock(success?: true) - end - - it 'proxies to the response object' do - _(resource._success?).must_equal true - end - end - - describe 'without a response object' do - let(:resource) do - Resource.new({}, entry_point) - end - - it 'returns nil' do - _(resource._success?).must_be_nil - end - end - end - - describe '._status' do - describe 'with a response object' do - let(:resource) do - Resource.new({}, entry_point, mock_response) - end - - let(:mock_response) do - mock(status: 200) - end - - it 'proxies to the response object' do - _(resource._status).must_equal 200 - end - end - - describe 'without a response object' do - let(:resource) do - Resource.new({}, entry_point) - end - - it 'returns nil' do - _(resource._status).must_be_nil - end - end - end - end -end diff --git a/test/hyperclient_test.rb b/test/hyperclient_test.rb deleted file mode 100644 index 011d06e..0000000 --- a/test/hyperclient_test.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'test_helper' -require 'hyperclient' - -describe Hyperclient do - describe 'new' do - it 'creates a new EntryPoint with the url' do - Hyperclient::EntryPoint.expects(:new).with('http://api.example.org/') - - Hyperclient.new('http://api.example.org/') - end - - describe 'with an optional block' do - let(:client) do - Hyperclient.new('http://api.example.org/') do |client| - client.connection(default: true) do |conn| - conn.use Faraday::Request::Instrumentation - end - client.headers['Access-Token'] = 'token' - end - end - - it 'creates a Faraday connection with the default and additional headers' do - _(client.headers['Content-Type']).must_equal 'application/hal+json' - _(client.headers['Accept']).must_equal 'application/hal+json,application/json' - _(client.headers['Access-Token']).must_equal 'token' - end - - it 'creates a Faraday connection with the entry point url' do - _(client.connection.url_prefix.to_s).must_equal 'http://api.example.org/' - end - - it 'creates a Faraday connection with the default block plus any additional handlers' do - handlers = client.connection.builder.handlers - - _(handlers).must_include Faraday::Request::Instrumentation - _(handlers).must_include Faraday::Response::RaiseError - _(handlers).must_include Faraday::FollowRedirects::Middleware - _(handlers).must_include Faraday::HalJson::Request - _(handlers).must_include Faraday::HalJson::Response - end - end - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb deleted file mode 100644 index f0a1d9c..0000000 --- a/test/test_helper.rb +++ /dev/null @@ -1,36 +0,0 @@ -$LOAD_PATH << 'lib' - -if ENV['COVERAGE'] - require 'simplecov' - require 'simplecov-lcov' - SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter - SimpleCov::Formatter::LcovFormatter.config do |c| - c.report_with_single_file = true - c.lcov_file_name = 'lcov.info' - c.single_report_path = 'coverage/lcov.info' - end - SimpleCov.start do - add_filter '/test/' - add_filter '/features/' - end -end - -require 'minitest/autorun' -require 'minitest/pride' -require 'minitest/unit' -require 'mocha/minitest' -require 'json' - -require 'faraday' -require 'faraday/request/instrumentation' - -MiniTest::Assertions.class_eval do - def stub_request(conn, adapter_class = Faraday::Adapter::Test, &stubs_block) - adapter_handler = conn.builder.handlers.find { |h| h.klass < Faraday::Adapter } - if adapter_handler - conn.builder.swap(adapter_handler, adapter_class, &stubs_block) - else - conn.builder.adapter adapter_class, &stubs_block - end - end -end