api-blueprint 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ec684c45d9e4a9a68fff3a249ccf2a55867fe871
4
- data.tar.gz: a3d9ae3d1a9cb681122a854f8f71b6e1860d8cf7
3
+ metadata.gz: 690bfec6246875178ddab86cb4b39c78543a689c
4
+ data.tar.gz: ea236a3bdd43e5ab463573d072f43d6bca9751a8
5
5
  SHA512:
6
- metadata.gz: 29850f1231fb34be81d8efcd579ca995741fe896ad2b0d91e4b9372c7c5d4d7e92e82d9b915bea884bafcb4b73fbbe879cb07f2c64481a7395e0158ad344516e
7
- data.tar.gz: aceaf3b5c6ce9e8b052fd0968947042922701e8b05e6c29b8bea29205115ba7d4a8fa521b859c69406c9fe5692629170bf7830520ec0c5bee85beb3026b0968f
6
+ metadata.gz: c9f5f9c9bba8fe2d8081cc2fbea82d8e8bb3e9fc48d6a720d1df88ec424fb3e5e70a4462e2a2d2d60ca41b97fdbd5725cc8cdcdbe41479bbd484ac0d67dbf620
7
+ data.tar.gz: f7b52ca2dac76e595625a3e4aec9e9ed68a9cf8f144c7979ae699e8a832e9e32380fdda2935ef6b0d1e43f46836d0eb09213599cfa155a699419e08db0166df5
data/README.md CHANGED
@@ -118,6 +118,37 @@ Astronaut.send_to_space(nil) # => <ActiveModel::Errors ...>
118
118
 
119
119
  Behind the scenes, ApiBlueprint uses the body hash to initialize a new instance of your model, and then runs validations. If there are any errors, the API request is not run and the errors object is returned.
120
120
 
121
+ ## Error handling
122
+
123
+ If an API response includes an `errors` object, ApiBlueprint uses it to assign `ActiveModel::Errors` instances on the class which is built. This way, validation errors which come an the API behave exactly the same as validation errors set locally through validations on the model.
124
+
125
+ Certain response statuses will also cause ApiBlueprint to behave in different ways:
126
+
127
+ | HTTP Status range | Behavior |
128
+ | ----------------- | -------- |
129
+ | 200 - 400 | Objects are built normally, no errors raised |
130
+ | 401 | raises `ApiBlueprint::UnauthenticatedError` |
131
+ | 402 - 499 | raises `ApiBlueprint::ClientError` |
132
+ | 500 - 599 | raises `ApiBlueprint::ServerError` |
133
+
134
+ ## Access to response headers and status codes
135
+
136
+ By default, ApiBlueprint tries to set `response_headers` and `response_status` on the model which is created from an API response. `ApiBlueprint::Model` also has a convenience method `api_request_success?` which can be used to easily assert whether a response was in the 200-399 range. This makes it simple to render different responses in controllers. For example:
137
+
138
+ ```ruby
139
+ # app/controllers/astronauts_controller.rb
140
+ class AstronautsController < ApplicationController
141
+ def index
142
+ @astronauts = api.run AstronautsInSpace.fetch
143
+ if @astronauts.api_request_success?
144
+ render json: @astronauts
145
+ else
146
+ render json: @astronauts.errors, status: :bad_request
147
+ end
148
+ end
149
+ end
150
+ ```
151
+
121
152
  ## Blueprint options
122
153
 
123
154
  When defining a blueprint in a model, you can pass it a number of options to set request headers, params, body, or to run code after an instance of the model has been initialized. Here's some examples:
@@ -32,7 +32,7 @@ module ApiBlueprint
32
32
  response = call_api all_request_options(options)
33
33
 
34
34
  if creates.present?
35
- created = build from: response.body, headers: response.headers
35
+ created = build from: response.body, headers: response.headers, status: response.status
36
36
  else
37
37
  created = response
38
38
  end
@@ -42,15 +42,18 @@ module ApiBlueprint
42
42
 
43
43
  private
44
44
 
45
- def build(from:, headers: {})
45
+ def build(from:, headers: {}, status: nil)
46
46
  builder_options = {
47
47
  body: parser.parse(from),
48
48
  headers: headers,
49
+ status: status,
49
50
  replacements: replacements,
50
51
  creates: creates
51
52
  }
52
53
 
53
- builder.new(builder_options).build
54
+ builder.new(builder_options).build.tap do |built|
55
+ set_errors built, builder_options[:body]
56
+ end
54
57
  end
55
58
 
56
59
  def call_api(options)
@@ -64,8 +67,8 @@ module ApiBlueprint
64
67
 
65
68
  def connection
66
69
  Faraday.new do |conn|
70
+ conn.use ApiBlueprint::ResponseMiddleware
67
71
  conn.response :json, content_type: /\bjson$/
68
- conn.response :raise_error
69
72
  # conn.response :logger
70
73
 
71
74
  conn.adapter Faraday.default_adapter
@@ -75,5 +78,24 @@ module ApiBlueprint
75
78
  end
76
79
  end
77
80
 
81
+ def set_errors(obj, body)
82
+ if obj.respond_to?(:errors) && body.is_a?(Hash)
83
+ errors = body.with_indifferent_access.fetch :errors, {}
84
+ errors.each do |field, messages|
85
+ if messages.is_a? Array
86
+ messages.each do |message|
87
+ set_error obj, field, message
88
+ end
89
+ else
90
+ set_error obj, field, messages
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ def set_error(obj, field, messages)
97
+ obj.errors.add field.to_sym, messages
98
+ end
99
+
78
100
  end
79
101
  end
@@ -4,6 +4,7 @@ module ApiBlueprint
4
4
 
5
5
  attribute :body, Types::Hash.default(Hash.new)
6
6
  attribute :headers, Types::Hash.default(Hash.new)
7
+ attribute :status, Types::Int.optional
7
8
  attribute :replacements, Types::Hash.default(Hash.new)
8
9
  attribute :creates, Types::Any
9
10
 
@@ -18,7 +19,12 @@ module ApiBlueprint
18
19
  end
19
20
 
20
21
  def prepare_item(item)
21
- with_replacements item.with_indifferent_access
22
+ meta = {
23
+ response_headers: headers,
24
+ response_status: status
25
+ }
26
+
27
+ meta.merge with_replacements(item.deep_symbolize_keys)
22
28
  end
23
29
 
24
30
  def build_item(item)
@@ -14,6 +14,9 @@ module ApiBlueprint
14
14
  setting :builder, Builder.new
15
15
  setting :replacements, {}
16
16
 
17
+ attribute :response_headers, Types::Hash.optional
18
+ attribute :response_status, Types::Int.optional
19
+
17
20
  def self.blueprint(http_method, url, options = {}, &block)
18
21
  blueprint_opts = {
19
22
  http_method: http_method,
@@ -35,5 +38,9 @@ module ApiBlueprint
35
38
  Collection.new blueprints, self
36
39
  end
37
40
 
41
+ def api_request_success?
42
+ response_status.present? && (200...299).include?(response_status)
43
+ end
44
+
38
45
  end
39
46
  end
@@ -5,6 +5,8 @@ module ApiBlueprint
5
5
  # to make a custom parser.
6
6
  def parse(body)
7
7
  body.is_a?(String) ? JSON.parse(body) : body
8
+ rescue JSON::ParserError
9
+ {}
8
10
  end
9
11
 
10
12
  end
@@ -0,0 +1,20 @@
1
+ module ApiBlueprint
2
+ class ResponseMiddleware < Faraday::Response::Middleware
3
+
4
+ def on_complete(env)
5
+ case env[:status]
6
+ when 401
7
+ raise ApiBlueprint::UnauthenticatedError, response_values(env)
8
+ when 402..499
9
+ raise ApiBlueprint::ClientError, response_values(env)
10
+ when 500...599
11
+ raise ApiBlueprint::ServerError, response_values(env)
12
+ end
13
+ end
14
+
15
+ def response_values(env)
16
+ { status: env.status, headers: env.response_headers, body: env.body }
17
+ end
18
+
19
+ end
20
+ end
@@ -1,3 +1,3 @@
1
1
  module ApiBlueprint
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
data/lib/api-blueprint.rb CHANGED
@@ -7,6 +7,7 @@ require 'active_model'
7
7
  require 'active_support/core_ext/hash'
8
8
  require 'addressable'
9
9
 
10
+ require 'api-blueprint/response_middleware'
10
11
  require 'api-blueprint/cache'
11
12
  require 'api-blueprint/types'
12
13
  require 'api-blueprint/url'
@@ -20,4 +21,7 @@ require 'api-blueprint/collection'
20
21
  module ApiBlueprint
21
22
  class DefinitionError < StandardError; end
22
23
  class BuilderError < StandardError; end
24
+ class ServerError < StandardError; end
25
+ class UnauthenticatedError < StandardError; end
26
+ class ClientError < StandardError; end
23
27
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api-blueprint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Damien Timewell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-13 00:00:00.000000000 Z
11
+ date: 2018-04-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-types
@@ -210,6 +210,7 @@ files:
210
210
  - lib/api-blueprint/collection.rb
211
211
  - lib/api-blueprint/model.rb
212
212
  - lib/api-blueprint/parser.rb
213
+ - lib/api-blueprint/response_middleware.rb
213
214
  - lib/api-blueprint/runner.rb
214
215
  - lib/api-blueprint/types.rb
215
216
  - lib/api-blueprint/url.rb