api-blueprint 0.5.0 → 0.6.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: 92b97a2c669c6b891dca65a5d31a2bfa703951bd
4
- data.tar.gz: 90dd0b51277c9d8e8279ada2be73e1255c61b75e
3
+ metadata.gz: bc247d9616a2e34feda3dd467150f03d384628c0
4
+ data.tar.gz: 68106129bf5eccc56440c0d15ae49f40034c7b20
5
5
  SHA512:
6
- metadata.gz: 9d36fd33e0683abc566069d8b2698d9317f12fa65e76f0bce55e313bfa43ceb5c1a9191d282cd01359221bd4b012dd6abcccf8b8f4813a10e3a648f184ca72e3
7
- data.tar.gz: 242e8eda32ad332e4ebf46e538dcb6173bcbaf8d293ab888ff7aa5a3f7e26fba00a411b295d5577de5b62223209e874c5044ca8d714f1cafaf73d628c943ec12
6
+ metadata.gz: cc963e572698777cded8128676784cbd2625c6b55f34931a274f7697d93bd57bf50e4ae91590a018841560472898aaf35400a00885e1e0929bfce59a0329c3ca
7
+ data.tar.gz: 05333cf34ddc87d344cfb5bd90bf8d37e94282b53a6d98114b8a67a04d99450e37dc2470a31aea443ba94b9c57e5eb2bcab17bec3d6beb2eaf6a7c75a00afe53
data/README.md CHANGED
@@ -61,6 +61,51 @@ The result of using `api.run` on a blueprint is as you'd expect, nice model inst
61
61
  </ul>
62
62
  ```
63
63
 
64
+ ## Collections
65
+
66
+ Sometimes you might want a model which requires multiple api calls and collects the results onto different attributes. You can use an `ApiBlueprint::Model.collection` for this.
67
+
68
+ ```ruby
69
+ class Vehicles < ApiBlueprint::Model
70
+ attribute :car, Types.Constructor(Car)
71
+ attribute :bus, Types.Constructor(Bus)
72
+
73
+ def self.fetch_all(color)
74
+ collection \
75
+ car: Car.all(color),
76
+ bus: Bus.all(color)
77
+ end
78
+ end
79
+
80
+ # Example use
81
+ red_vehicles = api.run Vehicles.fetch_all("red")
82
+ red_vehicles.cars # [<Car>, <Car>, ...]
83
+ red_vehicles.busses # [<Bus>, <Bus>, ...]
84
+ ```
85
+
86
+ ## Request registry
87
+
88
+ If you use the same api request in multiple controllers, it can be cumbersome to remember to set the cache options and pass all required params to api calls. ApiBlueprint includes a registry, which can be used as a container to store blueprints along with cache options and make it quicker and simpler to re-use in controllers.
89
+
90
+ You can add to the registry when initialing the api runner, or later.
91
+
92
+ ```ruby
93
+ # Add `astronauts_in_space` to the registry when initializing the runner:
94
+ api = ApiBlueprint::Runner.new registry: {
95
+ astronauts_in_space: { blueprint: -> { AstronautsInSpace.fetch }, cache: { ttl: 10.minutes } }
96
+ }
97
+
98
+ # Add `vehicles` to the existing registry:
99
+ api.register :vehicles, -> { Vehicles.fetch_all }, ttl: 60.minutes
100
+ ```
101
+
102
+ Once a blueprint is registered in the registry, you can invoke it via the key name on the runner:
103
+
104
+ ```ruby
105
+ api.astronauts_in_space # the same as running api.run AstronautsInSpace.fetch, ttl: 10.minutes
106
+ api.vehicles # the same as running api.run Vehicles.fetch_all, ttl: 60.minutes
107
+ ```
108
+
64
109
  ## Model Configuration
65
110
 
66
111
  Using a `configure` block on models, you can define a default url (host), a [parser](#configparser), a [builder](#configbuilder) and can define a list of [replacements](#configreplacements):
@@ -126,10 +171,10 @@ Certain response statuses will also cause ApiBlueprint to behave in different wa
126
171
 
127
172
  | HTTP Status range | Behavior |
128
173
  | ----------------- | -------- |
129
- | 200 - 399 | Objects are built normally, no errors raised |
174
+ | 200 - 400 | Objects are built normally, no errors raised |
130
175
  | 401 | raises `ApiBlueprint::UnauthenticatedError` |
131
176
  | 404 | raises `ApiBlueprint::NotFoundError` |
132
- | 400 - 499 | raises `ApiBlueprint::ClientError` |
177
+ | 402 - 499 | raises `ApiBlueprint::ClientError` |
133
178
  | 500 - 599 | raises `ApiBlueprint::ServerError` |
134
179
 
135
180
  ## Access to response headers and status codes
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/struct'
10
11
  require 'api-blueprint/response_middleware'
11
12
  require 'api-blueprint/cache'
12
13
  require 'api-blueprint/types'
@@ -19,10 +20,19 @@ require 'api-blueprint/runner'
19
20
  require 'api-blueprint/collection'
20
21
 
21
22
  module ApiBlueprint
23
+ class ResponseError < StandardError
24
+ attr_reader :status, :headers, :body
25
+ def initialize(env)
26
+ @status = env.status
27
+ @headers = env.response_headers.symbolize_keys
28
+ @body = env.body
29
+ end
30
+ end
31
+
22
32
  class DefinitionError < StandardError; end
23
33
  class BuilderError < StandardError; end
24
- class ServerError < StandardError; end
25
- class UnauthenticatedError < StandardError; end
26
- class ClientError < StandardError; end
27
- class NotFoundError < StandardError; end
34
+ class ServerError < ResponseError; end
35
+ class UnauthenticatedError < ResponseError; end
36
+ class ClientError < ResponseError; end
37
+ class NotFoundError < ResponseError; end
28
38
  end
@@ -1,14 +1,12 @@
1
1
  module ApiBlueprint
2
- class Blueprint < Dry::Struct
3
- constructor_type :schema
4
-
2
+ class Blueprint < ApiBlueprint::Struct
5
3
  attribute :http_method, Types::Symbol.default(:get).enum(*Faraday::Connection::METHODS)
6
4
  attribute :url, Types::String
7
5
  attribute :headers, Types::Hash.default(Hash.new)
8
6
  attribute :params, Types::Hash.default(Hash.new)
9
7
  attribute :body, Types::Hash.default(Hash.new)
10
8
  attribute :creates, Types::Any
11
- attribute :parser, Types.Instance(ApiBlueprint::Parser).default(ApiBlueprint::Parser.new)
9
+ attribute :parser, Types.Instance(ApiBlueprint::Parser).optional.default(ApiBlueprint::Parser.new)
12
10
  attribute :replacements, Types::Hash.default(Hash.new)
13
11
  attribute :after_build, Types::Instance(Proc).optional
14
12
  attribute :builder, Types.Instance(ApiBlueprint::Builder).default(ApiBlueprint::Builder.new)
@@ -1,12 +1,11 @@
1
1
  module ApiBlueprint
2
- class Builder < Dry::Struct
3
- constructor_type :schema
2
+ class Builder < ApiBlueprint::Struct
4
3
 
5
4
  attribute :body, Types::Hash.default(Hash.new)
6
5
  attribute :headers, Types::Hash.default(Hash.new)
7
- attribute :status, Types::Int.optional
6
+ attribute :status, Types::Integer.optional
8
7
  attribute :replacements, Types::Hash.default(Hash.new)
9
- attribute :creates, Types::Any
8
+ attribute :creates, Types::Any.optional
10
9
 
11
10
  attr_writer :body
12
11
 
@@ -1,5 +1,5 @@
1
1
  module ApiBlueprint
2
- class Model < Dry::Struct
2
+ class Model < ApiBlueprint::Struct
3
3
  extend Dry::Configurable
4
4
  include ActiveModel::Conversion
5
5
  include ActiveModel::Validations
@@ -7,15 +7,13 @@ module ApiBlueprint
7
7
  extend ActiveModel::Naming
8
8
  extend ActiveModel::Callbacks
9
9
 
10
- constructor_type :schema
11
-
12
10
  setting :host, ""
13
11
  setting :parser, Parser.new
14
12
  setting :builder, Builder.new
15
13
  setting :replacements, {}
16
14
 
17
15
  attribute :response_headers, Types::Hash.optional
18
- attribute :response_status, Types::Int.optional
16
+ attribute :response_status, Types::Integer.optional
19
17
 
20
18
  def self.blueprint(http_method, url, options = {}, &block)
21
19
  blueprint_opts = {
@@ -4,19 +4,15 @@ module ApiBlueprint
4
4
  def on_complete(env)
5
5
  case env[:status]
6
6
  when 401
7
- raise ApiBlueprint::UnauthenticatedError, response_values(env)
7
+ raise ApiBlueprint::UnauthenticatedError.new(env)
8
8
  when 404
9
- raise ApiBlueprint::NotFoundError, response_values(env)
9
+ raise ApiBlueprint::NotFoundError.new(env)
10
10
  when 402..499
11
- raise ApiBlueprint::ClientError, response_values(env)
11
+ raise ApiBlueprint::ClientError.new(env)
12
12
  when 500...599
13
- raise ApiBlueprint::ServerError, response_values(env)
13
+ raise ApiBlueprint::ServerError.new(env)
14
14
  end
15
15
  end
16
16
 
17
- def response_values(env)
18
- { status: env.status, headers: env.response_headers, body: env.body }
19
- end
20
-
21
17
  end
22
18
  end
@@ -4,6 +4,7 @@ module ApiBlueprint
4
4
 
5
5
  option :headers, default: proc { {} }
6
6
  option :cache, default: proc { Cache.new key: "global" }
7
+ option :registry, default: proc { {} }
7
8
 
8
9
  def run(item, cache_options = {})
9
10
  if item.is_a?(Blueprint)
@@ -19,6 +20,18 @@ module ApiBlueprint
19
20
  { headers: headers, cache: cache }
20
21
  end
21
22
 
23
+ def register(name, blueprint, cache_options = {})
24
+ registry[name] = { blueprint: blueprint, cache: cache_options }
25
+ end
26
+
27
+ def method_missing(name, *args, &block)
28
+ if stored_method = registry[name].presence
29
+ run stored_method[:blueprint].call(*args), stored_method[:cache]
30
+ else
31
+ raise NoMethodError, "#{name} is not defined in the ApiBlueprint::Runner registry"
32
+ end
33
+ end
34
+
22
35
  private
23
36
 
24
37
  def run_blueprint(blueprint, cache_options)
@@ -0,0 +1,9 @@
1
+ module ApiBlueprint
2
+ class Struct < Dry::Struct
3
+ transform_keys &:to_sym
4
+
5
+ transform_types do |type|
6
+ type.default? ? type : type.meta(omittable: true)
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module ApiBlueprint
2
- VERSION = '0.5.0'
2
+ VERSION = '0.6.0'
3
3
  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.5.0
4
+ version: 0.6.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-26 00:00:00.000000000 Z
11
+ date: 2018-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-types
@@ -212,6 +212,7 @@ files:
212
212
  - lib/api-blueprint/parser.rb
213
213
  - lib/api-blueprint/response_middleware.rb
214
214
  - lib/api-blueprint/runner.rb
215
+ - lib/api-blueprint/struct.rb
215
216
  - lib/api-blueprint/types.rb
216
217
  - lib/api-blueprint/url.rb
217
218
  - lib/api-blueprint/version.rb