/rest-service-client

Primary LanguageRubyMIT LicenseMIT

Rest Service Client

Gem Version

Installation

Add this line to your application's Gemfile:

gem 'rest-service-client'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install rest-service-client

Usage

Add this line to top of your file

require 'rest-service-client'

Create a class, include the RestServiceClient and configure your service

class MyAwesomeService
    include RestServiceClient
  
    # Sets the host url
    # You can use MyAwesomeService.new('https://jsonplaceholder.typicode.com')
    # to set the host instead. Using host url via constructor has high priority.
    host 'https://jsonplaceholder.typicode.com'

    # Sets the default headers for every requests.
    # Will be replaced using: MyAwesomeService.new.find_photo(id: 1, headers: { 'Authorization' => 'Bearer 123456' })
    headers Authorization: 'OAuth 1111|111'

    # Sets the default params for every requests.
    # Will be replaced using: MyAwesomeService.new.find_photo(id: 20)
    params id: 10

    # Configure endpoints:
    # _httpmethod _method_name, _path
    # "get :photos, '/photos'" provides an ability 
    # to make GET request to 'https://jsonplaceholder.typicode.com/photos'
    # using MyAwesomeService.new.photos
    # Supported: get, post, put, patch, delete
    get :photos, '/photos'
    
    # You can assign placeholders for params to path.
    # Example: MyAwesomeService.new.find_photo(id: 1)
    get :find_photo, '/photos/:id'
    
    post :add_photo, '/photos'
    put :update_photo, '/photos/:id'
    patch :update_photo_data, '/photos/:id'
    delete :delete_photo, '/photos/:id'
end

Samples

Simple Service

class SimpleService
  include RestServiceClient
  
  host 'https://jsonplaceholder.typicode.com'
  
  get :photo, '/photos/:id'
  get :first_photo, '/photos/1'
  get :second_photo, '/photos/2'
end

service = SimpleService.new

p service.first_photo.result['id']
# 1

p service.second_photo.result['id']
# 2

p service.photo(id: 10).result['id']
# 10

GET

class SimpleService
  include RestServiceClient
  host 'https://jsonplaceholder.typicode.com'
  get :photo, '/photos/:id', params: { id: 1 }
end

service = SimpleService.new
p service.photo.result['id']         # 1
p service.photo(id: 2).result['id']  # 2
class SimpleService
  include RestServiceClient
  host 'https://jsonplaceholder.typicode.com'
  get :first_photo, '/photos/1', headers: { Authentication: 'my-token' }
end

service = SimpleService.new
# Sends the request to 'https://jsonplaceholder.typicode.com/photos/1'
# includes Headers: { 'Authentication': 'my-token' }
p service.first_photo.result['albumId'] # 1

# Sends the request to 'https://jsonplaceholder.typicode.com/photos/1'
# includes Headers: { 
#   'Authentication': 'my-token',
#   'AnotherHeaderKey': 'AnotherHeaderValue' 
# }
another_headers = { AnotherHeaderKey: 'AnotherHeaderValue' }
photo = service.first_photo(headers: another_headers).result
p photo['albumId'] # 1

DELETE

class SimpleService
  include RestServiceClient
  host 'https://jsonplaceholder.typicode.com'
  delete :delete_photo, '/photos/:id'
end

service = SimpleService.new
service.delete_photo(id: 1).result

POST

class SimpleService
  include RestServiceClient
  host 'https://jsonplaceholder.typicode.com'
  post :add_photo, '/photos'
end

service = SimpleService.new
photo = {
    albumId: 1,
    title: 'new photo',
    url: 'http://placehold.it/600/92c952',
    thumbnailUrl: 'http://placehold.it/150/92c952'
}
response = service.add_photo(payload: photo)
p response.status # 201
p response.headers # {:date=>"...}
p response.result
=begin
{
  "albumId"=>1,
  "title"=>"new photo", 
  "url"=>"http://placehold.it/600/92c952",
  "thumbnailUrl"=>"http://placehold.it/150/92c952",
  "id"=>5001
}
=end

PATCH

class SimpleService
  include RestServiceClient
  host 'https://jsonplaceholder.typicode.com'
  patch :update_photo_data, '/photos/:id'
end

service = SimpleService.new
updated_photo = service.update_photo_data id: 1, payload: { title: 'new title'}
p updated_photo.result['title']
# "new title"

Debug mode

You can debug mode on for see what is happening

class SimpleService
  include RestServiceClient
  debug true
  host 'https://jsonplaceholder.typicode.com'
  headers key1: 'value1', 'key2' => 'value2'
  get :photo, 
      '/photos/:id', 
      params: { id: 1 }, 
      headers: { key3: 'value3' },
      payload: { data: { type: 'get_photo_by_id' }}
end

service = SimpleService.new
service.photo.result
=begin
 ______
|
|  SimpleService is processing GET request to https://jsonplaceholder.typicode.com/photos/1
|    Headers: {:key1=>"value1", "key2"=>"value2", :key3=>"value3"}
|    Payload: {:data=>{:type=>"get_photo_by_id"}}
|
|  SimpleService is processing the response on GET request to https://jsonplaceholder.typicode.com/photos/1
|    Status: 200
|    Body: {
  "albumId": 1,
  "id": 1,
  "title": "accusamus beatae ad facilis cum similique qui sunt",
  "url": "http://placehold.it/600/92c952",
  "thumbnailUrl": "http://placehold.it/150/92c952"
}
|______
=end

Serializer

By the default RestServiceClient uses JSON serializer and returns the arrays and hashes with key as string

You can use custom serializer. For do this, create a serializer class with self.deserialize method which gets the one argument with response body and returns whatever you want.

And you need to add your serializer to your service using: serializer MyAwesomSerializer

Errors

class SimpleService
  include RestServiceClient
  debug true
  host 'https://jsonplaceholder.typicode.com'
  get :bad_url, '/bad_url'
end

service = SimpleService.new
response = service.bad_url
p response.status # 404
p response.message # "404 Not Found"

Example

  class Photo
    attr_accessor :id, :title, :album_id
  end

  class PhotoSerializer
    class Object::String
      def underscore
        gsub(/::/, '/')
          .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
          .gsub(/([a-z\d])([A-Z])/, '\1_\2')
          .tr('-', '_')
          .downcase
      end
    end

    def self.deserialize(json)
      data = JSON.parse(json)

      return build_photo(data) if data.is_a? Hash
      return data.map { |p| build_photo(p) } if data.is_a? Array
    end

    def self.build_photo(data)
      object = Photo.new
      data.each_with_object(object) do |(k, v), o|
        setter = "#{k.underscore}=".to_sym
        o.send(setter, v) if o.respond_to?(setter)
      end
      object
    end
  end

  class Post
    attr_accessor :id, :title, :user_id
  end

  class PostSerializer
    def self.deserialize(json)
      data = JSON.parse(json)
      object = Post.new
      object.id = data['id']
      object.title = data['title']
      object.user_id = data['userId']
      object
    end
  end


  class MyService
    include RestServiceClient
    host 'https://jsonplaceholder.typicode.com'
    serializer PhotoSerializer
    get :photos, '/photos'
    get :find_photo, '/photos/:id'
    get :find_post, '/posts/:id', serializer: PostSerializer
  end

  service = MyService.new

  p service.find_photo(id: 1).result.album_id # 1
  p service.photos.result.find { |p| p.id == 3 }.id # 1
  p service.find_post(id: 1).result.user_id # 1

Response Object

Response

If request was completely sent, the class of response be RestServiceClient::Response This object has :status, :headers, :body and :result fields.

:status contains the number(integer) of http status code.

:headers contains the http headers from response.

:body will be a http body as plain text.

:result will contain deserialized http body.

ResponseWithError

If the request failed, the class of response will be RestServiceClient::ResponseWithError This object has :status, :headers, :body, :result and :message All fields except of :message are equals to RestServiceClient::Response fields.

:message will contain the string with error message

You are able to check the response before trying to use result to avoid exceptions:

class SimpleService
  include RestServiceClient
  host 'https://jsonplaceholder.typicode.com'
  get :get_photo, '/photos/:id' 
  get :bad_url, '/bad_url' 
end

service = SimpleService.new

response = service.get_photo id: 1
if response.status == 200
  p "Photo with name: #{response.result['name']} has been loaded"
end

response = service.bad_url
if response.status != 200
  p "Something was wrong. Reason: #{response.message}"
  # "Something was wrong. Reason: 404 Not Found" 
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/besya/rest-service-client. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the ServiceClient project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.