farscape 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +14 -0
- data/CONTRIBUTING.md +3 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +192 -0
- data/LICENSE.md +19 -0
- data/README.md +189 -0
- data/Rakefile +10 -0
- data/WRITING_PLUGINS.md +162 -0
- data/lib/farscape/agent.rb +113 -0
- data/lib/farscape/base_agent.rb +15 -0
- data/lib/farscape/cache.rb +13 -0
- data/lib/farscape/client/base_client.rb +27 -0
- data/lib/farscape/client/http_client.rb +99 -0
- data/lib/farscape/clients.rb +9 -0
- data/lib/farscape/errors.rb +172 -0
- data/lib/farscape/helpers/partially_ordered_list.rb +75 -0
- data/lib/farscape/logger.rb +13 -0
- data/lib/farscape/plugins.rb +71 -0
- data/lib/farscape/representor.rb +110 -0
- data/lib/farscape/transition.rb +81 -0
- data/lib/farscape/version.rb +6 -0
- data/lib/farscape.rb +4 -0
- data/lib/plugins/plugins.rb +104 -0
- data/spec/lib/farscape/cache_spec.rb +22 -0
- data/spec/lib/farscape/integration/entry_point_spec.rb +29 -0
- data/spec/lib/farscape/integration/interface_spec.rb +222 -0
- data/spec/lib/farscape/integration/representor_spec.rb +36 -0
- data/spec/lib/farscape/integration/resource_crud_spec.rb +92 -0
- data/spec/lib/farscape/logger_spec.rb +23 -0
- data/spec/lib/farscape/plugins_spec.rb +344 -0
- data/spec/lib/farscape/transition_spec.rb +79 -0
- data/spec/lib/helpers/partially_ordered_list_spec.rb +125 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/support/helpers.rb +5 -0
- data/tasks/yard.rake +15 -0
- metadata +221 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: aaaa9b7314cc73ef0fd43a9de2e7f17f78999636
|
4
|
+
data.tar.gz: ff6f62f5301162b952821b23a80be847fac1627e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 401dc3a255241d08565bc7067e6933b8450e48a97b87af028011e5600a41db720570731059486269daf4e95200c0f283cd5068666c90ff702699fa7de7e2022a
|
7
|
+
data.tar.gz: dbf7b5362bc58a66771ba67b8f8b822a17947eafc92ee5ca043019c3d2a76eb9907272919790b5dc23980e940f35f170e83f72724965509d19d20d2baf5ef5ef
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# 1.2.0
|
2
|
+
* Add support for templated URIs and POST requests
|
3
|
+
|
4
|
+
# 1.1.2
|
5
|
+
* Prevent crash when an embedded resource is not an enumerable
|
6
|
+
|
7
|
+
# 1.1.1
|
8
|
+
* Add POST method support
|
9
|
+
|
10
|
+
# 1.1.0
|
11
|
+
* Update Faraday dependency to ~> 0.9
|
12
|
+
|
13
|
+
# 0.0.1
|
14
|
+
* Initial Release
|
data/CONTRIBUTING.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
GIT
|
2
|
+
remote: git@github.com:mdsol/representors.git
|
3
|
+
revision: 958dc778d62897f09dd0200f3690f96cb5265b98
|
4
|
+
branch: 0-0-stable
|
5
|
+
specs:
|
6
|
+
representors (0.0.3)
|
7
|
+
addressable (~> 2.3)
|
8
|
+
rake
|
9
|
+
|
10
|
+
GIT
|
11
|
+
remote: https://www.github.com/mdsol-share/crichton.git
|
12
|
+
revision: 63c74ce781a41538454b81fd8c44dc36117aa92b
|
13
|
+
branch: develop
|
14
|
+
specs:
|
15
|
+
crichton (0.1.0)
|
16
|
+
activesupport (>= 3.2.0)
|
17
|
+
addressable (~> 2.3.0)
|
18
|
+
builder (>= 3.0.0)
|
19
|
+
colorize (~> 0.6.0)
|
20
|
+
dice_bag (~> 0.8)
|
21
|
+
diffy (~> 3.0.1)
|
22
|
+
i18n (>= 0.6.5)
|
23
|
+
nokogiri (>= 1.6.0)
|
24
|
+
rake
|
25
|
+
|
26
|
+
GIT
|
27
|
+
remote: https://www.github.com/mdsol/moya.git
|
28
|
+
revision: 6e8b324dee6335e5d56d320ba0a605b6ebae8574
|
29
|
+
branch: develop
|
30
|
+
specs:
|
31
|
+
moya (1.0.0)
|
32
|
+
activeuuid
|
33
|
+
launchy
|
34
|
+
nokogiri
|
35
|
+
rack
|
36
|
+
rails (= 4.1.8)
|
37
|
+
sqlite3
|
38
|
+
yajl-ruby (~> 1.2.0)
|
39
|
+
|
40
|
+
PATH
|
41
|
+
remote: .
|
42
|
+
specs:
|
43
|
+
farscape (1.0.1)
|
44
|
+
activesupport
|
45
|
+
addressable (~> 2.3.0)
|
46
|
+
dice_bag
|
47
|
+
faraday (~> 0.8.8)
|
48
|
+
faraday_middleware (~> 0.9)
|
49
|
+
rake
|
50
|
+
|
51
|
+
GEM
|
52
|
+
remote: https://rubygems.org/
|
53
|
+
specs:
|
54
|
+
actionmailer (4.1.8)
|
55
|
+
actionpack (= 4.1.8)
|
56
|
+
actionview (= 4.1.8)
|
57
|
+
mail (~> 2.5, >= 2.5.4)
|
58
|
+
actionpack (4.1.8)
|
59
|
+
actionview (= 4.1.8)
|
60
|
+
activesupport (= 4.1.8)
|
61
|
+
rack (~> 1.5.2)
|
62
|
+
rack-test (~> 0.6.2)
|
63
|
+
actionview (4.1.8)
|
64
|
+
activesupport (= 4.1.8)
|
65
|
+
builder (~> 3.1)
|
66
|
+
erubis (~> 2.7.0)
|
67
|
+
activemodel (4.1.8)
|
68
|
+
activesupport (= 4.1.8)
|
69
|
+
builder (~> 3.1)
|
70
|
+
activerecord (4.1.8)
|
71
|
+
activemodel (= 4.1.8)
|
72
|
+
activesupport (= 4.1.8)
|
73
|
+
arel (~> 5.0.0)
|
74
|
+
activesupport (4.1.8)
|
75
|
+
i18n (~> 0.6, >= 0.6.9)
|
76
|
+
json (~> 1.7, >= 1.7.7)
|
77
|
+
minitest (~> 5.1)
|
78
|
+
thread_safe (~> 0.1)
|
79
|
+
tzinfo (~> 1.1)
|
80
|
+
activeuuid (0.6.0)
|
81
|
+
activerecord (>= 3.1)
|
82
|
+
uuidtools
|
83
|
+
addressable (2.3.7)
|
84
|
+
arel (5.0.1.20140414130214)
|
85
|
+
awesome_print (1.1.0)
|
86
|
+
builder (3.2.2)
|
87
|
+
coderay (1.1.0)
|
88
|
+
colorize (0.6.0)
|
89
|
+
crack (0.4.2)
|
90
|
+
safe_yaml (~> 1.0.0)
|
91
|
+
dice_bag (0.8.0)
|
92
|
+
rake
|
93
|
+
diff-lcs (1.2.5)
|
94
|
+
diffy (3.0.7)
|
95
|
+
docile (1.1.5)
|
96
|
+
erubis (2.7.0)
|
97
|
+
faraday (0.8.9)
|
98
|
+
multipart-post (~> 1.2.0)
|
99
|
+
faraday_middleware (0.9.1)
|
100
|
+
faraday (>= 0.7.4, < 0.10)
|
101
|
+
hike (1.2.3)
|
102
|
+
i18n (0.7.0)
|
103
|
+
json (1.8.2)
|
104
|
+
launchy (2.4.3)
|
105
|
+
addressable (~> 2.3)
|
106
|
+
mail (2.6.3)
|
107
|
+
mime-types (>= 1.16, < 3)
|
108
|
+
method_source (0.8.2)
|
109
|
+
mime-types (2.4.3)
|
110
|
+
mini_portile (0.6.2)
|
111
|
+
minitest (5.5.1)
|
112
|
+
multi_json (1.11.0)
|
113
|
+
multipart-post (1.2.0)
|
114
|
+
nokogiri (1.6.6.2)
|
115
|
+
mini_portile (~> 0.6.0)
|
116
|
+
pry (0.10.1)
|
117
|
+
coderay (~> 1.1.0)
|
118
|
+
method_source (~> 0.8.1)
|
119
|
+
slop (~> 3.4)
|
120
|
+
rack (1.5.2)
|
121
|
+
rack-test (0.6.3)
|
122
|
+
rack (>= 1.0)
|
123
|
+
rails (4.1.8)
|
124
|
+
actionmailer (= 4.1.8)
|
125
|
+
actionpack (= 4.1.8)
|
126
|
+
actionview (= 4.1.8)
|
127
|
+
activemodel (= 4.1.8)
|
128
|
+
activerecord (= 4.1.8)
|
129
|
+
activesupport (= 4.1.8)
|
130
|
+
bundler (>= 1.3.0, < 2.0)
|
131
|
+
railties (= 4.1.8)
|
132
|
+
sprockets-rails (~> 2.0)
|
133
|
+
railties (4.1.8)
|
134
|
+
actionpack (= 4.1.8)
|
135
|
+
activesupport (= 4.1.8)
|
136
|
+
rake (>= 0.8.7)
|
137
|
+
thor (>= 0.18.1, < 2.0)
|
138
|
+
rake (0.9.6)
|
139
|
+
redcarpet (3.2.2)
|
140
|
+
rspec (2.99.0)
|
141
|
+
rspec-core (~> 2.99.0)
|
142
|
+
rspec-expectations (~> 2.99.0)
|
143
|
+
rspec-mocks (~> 2.99.0)
|
144
|
+
rspec-core (2.99.2)
|
145
|
+
rspec-expectations (2.99.2)
|
146
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
147
|
+
rspec-mocks (2.99.3)
|
148
|
+
safe_yaml (1.0.4)
|
149
|
+
simplecov (0.9.2)
|
150
|
+
docile (~> 1.1.0)
|
151
|
+
multi_json (~> 1.0)
|
152
|
+
simplecov-html (~> 0.9.0)
|
153
|
+
simplecov-html (0.9.0)
|
154
|
+
slop (3.6.0)
|
155
|
+
sprockets (2.12.3)
|
156
|
+
hike (~> 1.2)
|
157
|
+
multi_json (~> 1.0)
|
158
|
+
rack (~> 1.0)
|
159
|
+
tilt (~> 1.1, != 1.3.0)
|
160
|
+
sprockets-rails (2.2.4)
|
161
|
+
actionpack (>= 3.0)
|
162
|
+
activesupport (>= 3.0)
|
163
|
+
sprockets (>= 2.8, < 4.0)
|
164
|
+
sqlite3 (1.3.10)
|
165
|
+
thor (0.19.1)
|
166
|
+
thread_safe (0.3.5)
|
167
|
+
tilt (1.4.1)
|
168
|
+
tzinfo (1.2.2)
|
169
|
+
thread_safe (~> 0.1)
|
170
|
+
uuidtools (2.1.5)
|
171
|
+
webmock (1.13.0)
|
172
|
+
addressable (>= 2.2.7)
|
173
|
+
crack (>= 0.3.2)
|
174
|
+
yajl-ruby (1.2.1)
|
175
|
+
yard (0.8.7.6)
|
176
|
+
|
177
|
+
PLATFORMS
|
178
|
+
ruby
|
179
|
+
|
180
|
+
DEPENDENCIES
|
181
|
+
awesome_print (~> 1.1.0)
|
182
|
+
crichton!
|
183
|
+
farscape!
|
184
|
+
moya!
|
185
|
+
pry
|
186
|
+
rake (~> 0.9)
|
187
|
+
redcarpet
|
188
|
+
representors!
|
189
|
+
rspec (~> 2.14)
|
190
|
+
simplecov (~> 0.7)
|
191
|
+
webmock (~> 1.13.0)
|
192
|
+
yard (~> 0.8.5)
|
data/LICENSE.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2013 Medidata Solutions Worldwide
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
# Farscape
|
2
|
+
[![Build Status](https://travis-ci.org/mdsol/farscape.svg)](https://travis-ci.org/mdsol/farscape)
|
3
|
+
|
4
|
+
Farscape is a hypermedia agent that simplifies consuming Hypermedia API responses. It shoots through wormholes with
|
5
|
+
[Crichton](https://github.com/mdsol/crichton) at the helm and takes you to unknown places in the universe!
|
6
|
+
|
7
|
+
Checkout the [Documentation][] for more info.
|
8
|
+
|
9
|
+
NOTE: THIS IS UNDER HEAVY DEV AND IS NOT READY TO BE USED YET
|
10
|
+
|
11
|
+
|
12
|
+
## API Entry
|
13
|
+
There are various flavors of configuration that Farscape supports for entering a Hypermedia API. These all assume
|
14
|
+
a response with a supported Hypermedia media-type and a root that lists available resources as links.
|
15
|
+
|
16
|
+
### A Hypermedia API
|
17
|
+
For a interacting with an API (or individual service that supports a list of resources at its root), you enter the
|
18
|
+
API and follow your nose using the `enter` method on the agent. This method returns a [Farscape::Representor]()
|
19
|
+
instance with a simple state-machine interface of `attributes` (data) and `transitions` (link/form affordances) for
|
20
|
+
interacting with the resource representations.
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
agent = Farscape::Agent.new('http://example.com/my_api')
|
24
|
+
|
25
|
+
resources = agent.enter
|
26
|
+
resources.attributes # => { meta: 'data', or: 'other data' }
|
27
|
+
resources.transitions.keys # => ['http://example.com/rel/drds', 'http://example.com/rel/leviathans']
|
28
|
+
```
|
29
|
+
|
30
|
+
### A Hypermedia Discovery Service
|
31
|
+
For interacting with a discovery service, Farscape supports follow your nose entry to select a registered resource
|
32
|
+
or immediately loading a discoverable resource if known to be registered in the service *a priori*.
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
agent = Farscape::Agent.new('http://example.com/my_discovery_service')
|
36
|
+
|
37
|
+
resources = agent.enter
|
38
|
+
resources.attributes # => { meta: 'data', or: 'other data' }
|
39
|
+
resources.transitions.keys # => ['http://example.com/rel/drds', 'http://example.com/rel/leviathans', 'next', 'last']
|
40
|
+
|
41
|
+
drds = agent.enter('http://example.com/rel/drds')
|
42
|
+
drds.attributes # => { total_count: 25, items: [...] }
|
43
|
+
drds.transitions.keys # => ['self', 'search', 'create', 'next', 'last']
|
44
|
+
|
45
|
+
agent.enter('http://example.com/rel/unknown_resource') # => raises Farscape::Agent::UnknownEntryPoint
|
46
|
+
```
|
47
|
+
|
48
|
+
## API Interaction
|
49
|
+
Entering an API takes you into its application state-machine and, as such, the interface for interacting with that
|
50
|
+
application state is brain dead simple with Farscape. You have data that you read and hypermedia affordances that tell
|
51
|
+
you what you can do next and you can invoke those affordances to do things. That's it.
|
52
|
+
|
53
|
+
Farscape recognizes a number of media-types that support runtime knowledge of the underlying REST uniform-interface
|
54
|
+
methods. For these full-featured media-types, the interaction with with resources is as simple as a browser where
|
55
|
+
implementation of requests is completely abstracted from the user.
|
56
|
+
|
57
|
+
The following simple examples highlight interacting with resource state-machines using Farscape.
|
58
|
+
|
59
|
+
### Load a resource
|
60
|
+
```ruby
|
61
|
+
resources = agent.enter
|
62
|
+
drds_transition = resources.transitions['http://example.com/rel/drds']
|
63
|
+
drds = drds_transition.invoke
|
64
|
+
```
|
65
|
+
|
66
|
+
### Reload a resource
|
67
|
+
```ruby
|
68
|
+
self_transition = drds.transitions['self']
|
69
|
+
reloaded_drds = self_transition.invoke
|
70
|
+
```
|
71
|
+
|
72
|
+
### Explore
|
73
|
+
|
74
|
+
The sample code given below often depicts the client making *assumptions* that a specific transition or attribute will be available in a certain state. *This is unsafe*, and production code should include conditionals or rescues for the case when an assumption proves incorrect. Whenever possible, Farscape should be used more dynamically, by letting user interaction or a crawling algorithm drive transitions.
|
75
|
+
|
76
|
+
### Apply query parameters
|
77
|
+
```ruby
|
78
|
+
search_transition = drds.transitions['search']
|
79
|
+
search_transition.parameters # => ['search_term']
|
80
|
+
|
81
|
+
filtered_drds = search_transition.invoke do |builder|
|
82
|
+
builder.parameters = { search_term: '1812' }
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
You may also invoke transitions with automatic attribute and parameter matching
|
87
|
+
```ruby
|
88
|
+
drds.transitions['search'].invoke(search_term: '1812')
|
89
|
+
```
|
90
|
+
|
91
|
+
### Transform resource state
|
92
|
+
```ruby
|
93
|
+
embedded_drd_items = drds.items
|
94
|
+
|
95
|
+
drd = embedded_drd_items.first
|
96
|
+
drd.attributes # => { name: '1812' }
|
97
|
+
drd.transitions # => ['self', 'edit', 'delete', 'deactivate', 'leviathan']
|
98
|
+
|
99
|
+
deactivate_transition = drd.transitions['deactivate']
|
100
|
+
|
101
|
+
deactivated_drd = deactivate_transition.invoke
|
102
|
+
deactivated_drd.attributes # => { name: '1812' }
|
103
|
+
deactivated_drd.transitions # => ['self', 'activate', 'leviathan']
|
104
|
+
|
105
|
+
deactivate_transition.invoke # => raise Farscape::Excpetions::Gone error
|
106
|
+
```
|
107
|
+
|
108
|
+
### Transform application state
|
109
|
+
```ruby
|
110
|
+
leviathan_transition = deactivated_drd.transitions['leviathan']
|
111
|
+
|
112
|
+
leviathan = leviathan_transition.invoke
|
113
|
+
leviathan.attributes # => { name: 'Elack' }
|
114
|
+
leviathan.transitions # => ['self', 'drds']
|
115
|
+
```
|
116
|
+
|
117
|
+
### Use attributes
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
create_transition = drds.transitions['create']
|
121
|
+
create_transition.attributes # => ['name']
|
122
|
+
|
123
|
+
new_drd = create_transition.invoke do |builder|
|
124
|
+
builder.attributes = { name: 'Pike' }
|
125
|
+
end
|
126
|
+
|
127
|
+
new_drd.attributes # => { name: 'Pike' }
|
128
|
+
new_drd.transitions.keys # => ['self', 'edit', 'delete', 'deactivate', 'leviathan']
|
129
|
+
```
|
130
|
+
|
131
|
+
For more examples and information on using Faraday with media-types that require specifying uniform-interface methods
|
132
|
+
and other protocol idioms when invoking transitions, see [Using Farscape]().
|
133
|
+
|
134
|
+
## Alternate Interface
|
135
|
+
|
136
|
+
For developers more used to ActiveRecord syntax, Farscape resources also expose all transitions and attributes as Ruby methods. Safe (i.e. read) transitions are exposed verbatim.
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
drd.leviathan # => Equivalent to drd.transitions['leviathan'].invoke
|
140
|
+
```
|
141
|
+
|
142
|
+
Unsafe transitions have an exclamation point at the end.
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
drd.deactivate # => Raises NoMethodError
|
146
|
+
|
147
|
+
drd.deactivate! # => Equivalent to drd = drd.transitions['deactivate'].invoke
|
148
|
+
```
|
149
|
+
|
150
|
+
Request parameters can be passed as a hash or as a block.
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
# The following are all equivalent:
|
154
|
+
|
155
|
+
drd = drds.create!(name: 'Pike')
|
156
|
+
drd = drds.create! { |builder| builder.attributes = {name: 'Pike'} }
|
157
|
+
drd = drds.transitions['create'].invoke{ |d| d.attributes = {name: 'Pike'} }
|
158
|
+
```
|
159
|
+
|
160
|
+
Attributes are read-only.
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
drd.name # => "Pike"
|
164
|
+
|
165
|
+
drd.name = 'Susan' # => Raises NoMethodError
|
166
|
+
```
|
167
|
+
|
168
|
+
If an attribute or transition's name conflicts with an existing method or reserved word, it will not be methodized and must be accessed through the hash interface.
|
169
|
+
|
170
|
+
### Disabling the Alternate Interface
|
171
|
+
If you're concerned about namespace collisions, or want to ensure that your code is highly flexible and explicit (albeit verbose), you may turn off the interface with the .safe method.
|
172
|
+
```ruby
|
173
|
+
safe_drd = drd.safe # => returns a drd resource without the alternate interface
|
174
|
+
safe_drd.name # => UndefinedMethod error
|
175
|
+
drd.name # => "Pike"
|
176
|
+
```
|
177
|
+
|
178
|
+
You may reenable the alternate interface with `.unsafe`.
|
179
|
+
|
180
|
+
## Contributing
|
181
|
+
See [CONTRIBUTING][] for details.
|
182
|
+
|
183
|
+
## Copyright
|
184
|
+
Copyright (c) 2013 Medidata Solutions Worldwide. See [LICENSE][] for details.
|
185
|
+
|
186
|
+
[Crichton]: https://github.com/mdsol/crichton
|
187
|
+
[CONTRIBUTING]: CONTRIBUTING.md
|
188
|
+
[Documentation]: http://rubydoc.info/github/mdsol/farscape/develop/file/README.md
|
189
|
+
[LICENSE]: LICENSE.md
|
data/Rakefile
ADDED
data/WRITING_PLUGINS.md
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# Writing Plugins
|
2
|
+
|
3
|
+
Farscape Plugins can be used to add drop-in functionality to Farscape.
|
4
|
+
|
5
|
+
## Farscape Plugin Hash
|
6
|
+
|
7
|
+
A Farscape plugin is described with a hash with a set of well defined keys
|
8
|
+
|
9
|
+
#### name
|
10
|
+
This is a symbol representing the name of the plugin
|
11
|
+
|
12
|
+
#### type
|
13
|
+
The type of plugin, this is useful for manipulating multiple plguins simultaneously
|
14
|
+
|
15
|
+
#### middleware
|
16
|
+
Middleware objects as per [Faraday plugins](https://github.com/lostisland/faraday#writing-middleware)
|
17
|
+
|
18
|
+
#### extensions
|
19
|
+
A hash whos keys are the Farscape clases to be extended and values are a list of classes which will extend the default functionality of Farscape clases.
|
20
|
+
Possible keys are: `:Agent`, `:HttpClient`, `:SafeRepresentorAgent`, `:RepresentorAgent`, and `:TransitionAgent`
|
21
|
+
|
22
|
+
#### default_state
|
23
|
+
When registered, is this :enabled or :disabled by default it is :enabled
|
24
|
+
|
25
|
+
### Example
|
26
|
+
```ruby
|
27
|
+
{
|
28
|
+
name: :PluginName
|
29
|
+
type: :example_type
|
30
|
+
middleware: PluginMiddlewareClass
|
31
|
+
extension: { :Agent => [ExtenstionClass], :RepresentorAgent => [ExtensionClass, Walrus] }
|
32
|
+
default_state: :enabled
|
33
|
+
}
|
34
|
+
```
|
35
|
+
|
36
|
+
# Registration
|
37
|
+
|
38
|
+
To register your plugin, run
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
Farscape.register_plugin(name: :Cachinator, type: :cache, ...)
|
42
|
+
```
|
43
|
+
|
44
|
+
Feel free to put this code at the bottom of `cachinator.rb` so that it turns on automatically after the gem is loaded. Consumers who want control can include a line in their initializer reading `Farscape.disable!(:Cachinator)` or `Farscape.disable!(:cache)`. If you'd rather have your plugin be off by default, you could instead wrap the register_plugin call in, say, a `Cachinator.activate` method for the consumer to call as desired. If your plugin is http-specific (say it adds Authentication headers), include `protocol: :http`.
|
45
|
+
|
46
|
+
# Managing Plugins
|
47
|
+
Of the set of registered plugins, some will be enabled, and others disabled. Enabled plugins will apply their specified functionality to any current Farscape instance.
|
48
|
+
|
49
|
+
## Introspecting plguins
|
50
|
+
|
51
|
+
You can get a list of plugins with the `Farscape.plugins` method. If you want only the enabled plugins you can use `Farscape.enabled_plugins`, and if you want only disabled plguins you can do `Farscape.disabled_plugins`.
|
52
|
+
|
53
|
+
## Enabling and Disabling plugins
|
54
|
+
|
55
|
+
The default state of a plugin is defined by the Plugin Hash. To enable a disabled plugin you need to call `Farscape.enable!(options)` where options is a hash containing a `:name` key or a `:type` key. The name key will only enable plugins with the matching name, whereas type will enable all plugins of that type.
|
56
|
+
Likewise you can disable plugins with `Farscape.disable!(options)`, in which case you disable the plugins.
|
57
|
+
|
58
|
+
## Example Enable / Disable Workflow
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
Farscape.register_plugin({name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]})
|
62
|
+
Farscape.plugins #=> [{name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}]
|
63
|
+
Farscape.enabled_plugins #=> [{name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}]
|
64
|
+
Farscape.disabled_plugins #=> []
|
65
|
+
Farscape.disable!(type: :sebacean)
|
66
|
+
Farscape.enabled_plugins #=> []
|
67
|
+
Farscape.disabled_plugins #=> [{name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}]
|
68
|
+
Farscape.enable!(name: :Peacekeeper)
|
69
|
+
Farscape.enabled_plugins #=> [{name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}]
|
70
|
+
Farscape.disabled_plugins #=> []
|
71
|
+
```
|
72
|
+
|
73
|
+
## Plugins on instances
|
74
|
+
|
75
|
+
Plugins can also be modified on a per instance basis. Agent, SafeRepresentorAgent, and Representor all support #plugins, #enabled_plugins, and #disabled_plugins on a per instance basis. These behave the same as their associated Farscape class methods, except they communicate which plugins are enabled or diables for that instance only.
|
76
|
+
When an object creates a new object (for example `Agent.new(entry).enter` returns a RepresentorAgent), those instances will have the same plugins enabled as their parent.
|
77
|
+
|
78
|
+
### Enabling and Disabling Plugins for Instances
|
79
|
+
|
80
|
+
To enable plugins on an instance invoke the `using(name_or_type)` method, to disable plugins on an instance invoke the `omitting(name_or_type)` method. These methods will return a new instance with the associated plugin(s) in their new state. They do not modify the original object.
|
81
|
+
|
82
|
+
### Example instance workflow
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
Farscape.enabled_plugins #=> [{name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}]
|
86
|
+
agent = Farscape.Agent.new.omitting(name: :Peacekeeper)
|
87
|
+
agent.plugins # => [{name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}]
|
88
|
+
agent.enabled_plugins # => []
|
89
|
+
agent.plugins # => [{name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}]
|
90
|
+
resource = agent.enter(entry_point).transitions[:listing].invoke
|
91
|
+
resource.plugins # => [{name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}]
|
92
|
+
resource.enabled_plugins # => []
|
93
|
+
resource.plugins # => [{name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}]
|
94
|
+
details = resource.using(name: :Peacekeeper).transitions[:items][0].invoke
|
95
|
+
details.plugins # => [{name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}]
|
96
|
+
details.enabled_plugins # => []
|
97
|
+
details.disabled_plugins # => [{name: :Peacekeeper, type: :sebacean, middleware: [TestMiddleware::NoGetNoProblem]}]
|
98
|
+
```
|
99
|
+
|
100
|
+
# Adding Middleware
|
101
|
+
|
102
|
+
You can probably do what you need to do by writing [Faraday](https://github.com/lostisland/faraday)-style middleware. Middleware can inspect and alter outgoing requests and incoming responses, abort requests, and define hooks that run after a request/response cycle completes. All it needs to do is obey [a simple API](https://github.com/lostisland/faraday#writing-middleware).
|
103
|
+
|
104
|
+
To add your middleware to the stack, run
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
Farscape.register_plugin(name: :Cachinator, type: :cache, middleware: [Cachinator::Middleware], ...)
|
108
|
+
```
|
109
|
+
|
110
|
+
If you need to partially order your middleware, the elements of the middleware array can be hashes of the form:
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
{ class: Cachinator::Middleware,
|
114
|
+
before: RequestSigner,
|
115
|
+
after: ['HubSubscriber', 'Ouroborous', :authorization]
|
116
|
+
}
|
117
|
+
```
|
118
|
+
|
119
|
+
In this example, Cachinator::Middleware will be inserted before the RequestSigner middleware if it is present, and after the latest of HubSubscriber, Ouroborous, or any middleware of type "authorization". Note that Middleware classes can and should be given as strings if your plugin is not providing them, so that Ruby won't throw a NameError if they are undefined. If the ordering constraints given are impossible to satisfy, Farscape will throw an error.
|
120
|
+
|
121
|
+
You can also add config to your middleware when passing it in hash form:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
{ class: Cachinator::Middleware,
|
125
|
+
config: MyApp.config[:cache]
|
126
|
+
}
|
127
|
+
```
|
128
|
+
|
129
|
+
The config hash will be passed to your middleware as a second argument to `new`, as in Faraday. To pass multiple arguments, use `config: [arg1, arg2]`.
|
130
|
+
|
131
|
+
# Extending Agent
|
132
|
+
|
133
|
+
If you want to provide a more interactive API, or reference the deserialized response using the Representor interface, you can define a module that will be available to mix in to Farscape::Agent.
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
module Peacekeeper
|
137
|
+
def pacify!
|
138
|
+
raise if representor.transitions.keys.include?(:attack)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
Farscape.register_plugin(name: :Peacekeeper, type: :security, extensions: [Peacekeeper], extends: [:Agent])
|
142
|
+
agent.enter(url).using(:Peacekeeper).pacify!
|
143
|
+
```
|
144
|
+
|
145
|
+
# Farscape Utilities
|
146
|
+
|
147
|
+
Any plugin can reference `Farscape.cache`, which exposes [the same API as Rails.cache](http://apidock.com/rails/ActiveSupport/Cache/Store), `Farscape.logger`, which exposes [the same API as the built-in Ruby logger](http://apidock.com/ruby/Logger). By default, Farscape.cache operates in-memory and Farscape.logger writes to STDOUT. Plugins can provide enhanced versions of these utilities by modifying the global state of the Farscape object:
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
module Peacekeeper
|
151
|
+
class DalliCache
|
152
|
+
# code that implements the Cache api
|
153
|
+
end
|
154
|
+
end
|
155
|
+
Farscape.cache = Peacekeeper::DalliCache.new(config)
|
156
|
+
```
|
157
|
+
|
158
|
+
Future updates will provide Farscape.jobs, a backgrounding utility along similar lines.
|
159
|
+
|
160
|
+
# Creating a Client
|
161
|
+
|
162
|
+
By default, Farscape uses the [Net::HTTP](http://ruby-doc.org/stdlib-2.1.5/libdoc/net/http/rdoc/Net/HTTP.html) library to make HTTP requests. You can replace this client with `Faraday.clients[:http] = MyClient` or define one for a new protocol with `Faraday.clients[:amqp] = Jessica::Rabbit`. When a Farscape agent follows a link with a given protocol, it will use the client for that protocol if one has been provided. Required interface tk.
|