rsbe-client 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +3 -0
  4. data/GETTING-STARTED.md +17 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +73 -0
  8. data/Rakefile +11 -0
  9. data/TODO.md +38 -0
  10. data/lib/rsbe/client.rb +50 -0
  11. data/lib/rsbe/client/base.rb +140 -0
  12. data/lib/rsbe/client/collection.rb +84 -0
  13. data/lib/rsbe/client/connection.rb +24 -0
  14. data/lib/rsbe/client/method_not_implemented_error.rb +6 -0
  15. data/lib/rsbe/client/not_found_error.rb +6 -0
  16. data/lib/rsbe/client/partner.rb +82 -0
  17. data/lib/rsbe/client/provider.rb +47 -0
  18. data/lib/rsbe/client/se.rb +97 -0
  19. data/lib/rsbe/client/search.rb +80 -0
  20. data/lib/rsbe/client/search_results.rb +41 -0
  21. data/lib/rsbe/client/unrecognized_resource_error.rb +6 -0
  22. data/lib/rsbe/client/version.rb +5 -0
  23. data/lib/rsbe/client/wrong_origin_error.rb +6 -0
  24. data/rsbe-client.gemspec +30 -0
  25. data/spec/fixtures/partners/partners_all.json +1 -0
  26. data/spec/fixtures/partners/partners_none.json +1 -0
  27. data/spec/rsbe/client/base_spec.rb +10 -0
  28. data/spec/rsbe/client/collection_spec.rb +186 -0
  29. data/spec/rsbe/client/connection_spec.rb +20 -0
  30. data/spec/rsbe/client/partner_spec.rb +185 -0
  31. data/spec/rsbe/client/provider_spec.rb +122 -0
  32. data/spec/rsbe/client/se_spec.rb +273 -0
  33. data/spec/rsbe/client/search_results_spec.rb +140 -0
  34. data/spec/rsbe/client/search_spec.rb +74 -0
  35. data/spec/rsbe/client_spec.rb +52 -0
  36. data/spec/spec_helper.rb +43 -0
  37. data/spec/vcr_cassettes/client/find_collection.yml +53 -0
  38. data/spec/vcr_cassettes/client/find_partner.yml +52 -0
  39. data/spec/vcr_cassettes/client/find_provider.yml +52 -0
  40. data/spec/vcr_cassettes/client/find_se.yml +53 -0
  41. data/spec/vcr_cassettes/collection/find-existing.yml +53 -0
  42. data/spec/vcr_cassettes/collection/find-non_existent.yml +50 -0
  43. data/spec/vcr_cassettes/collection/lazy-eval-dne.yml +285 -0
  44. data/spec/vcr_cassettes/collection/lazy-eval-exists.yml +53 -0
  45. data/spec/vcr_cassettes/collection/save-create-known-id.yml +103 -0
  46. data/spec/vcr_cassettes/collection/save-create-unknown-id.yml +56 -0
  47. data/spec/vcr_cassettes/collection/save-invalid.yml +143 -0
  48. data/spec/vcr_cassettes/collection/save-update-valid.yml +99 -0
  49. data/spec/vcr_cassettes/partner/all.yml +300 -0
  50. data/spec/vcr_cassettes/partner/dne-with-id.yml +238 -0
  51. data/spec/vcr_cassettes/partner/find-existing.yml +52 -0
  52. data/spec/vcr_cassettes/partner/find-non_existent.yml +50 -0
  53. data/spec/vcr_cassettes/partner/lazy-eval-exists.yml +53 -0
  54. data/spec/vcr_cassettes/partner/save-create-known-id.yml +101 -0
  55. data/spec/vcr_cassettes/partner/save-create-unknown-id.yml +54 -0
  56. data/spec/vcr_cassettes/partner/save-invalid.yml +177 -0
  57. data/spec/vcr_cassettes/partner/save-unknown-id.yml +54 -0
  58. data/spec/vcr_cassettes/partner/save-update-valid.yml +98 -0
  59. data/spec/vcr_cassettes/partner/with-collections.yml +203 -0
  60. data/spec/vcr_cassettes/partner/without-collections.yml +101 -0
  61. data/spec/vcr_cassettes/provider/all.yml +248 -0
  62. data/spec/vcr_cassettes/provider/find-existing.yml +52 -0
  63. data/spec/vcr_cassettes/provider/find-non_existent.yml +50 -0
  64. data/spec/vcr_cassettes/provider/save-create-known-id.yml +101 -0
  65. data/spec/vcr_cassettes/provider/save-create-unknown-id.yml +54 -0
  66. data/spec/vcr_cassettes/provider/save-invalid.yml +178 -0
  67. data/spec/vcr_cassettes/provider/save-update-valid.yml +144 -0
  68. data/spec/vcr_cassettes/se/find-existing.yml +53 -0
  69. data/spec/vcr_cassettes/se/find-non_existent.yml +50 -0
  70. data/spec/vcr_cassettes/se/lazy-eval-exists.yml +52 -0
  71. data/spec/vcr_cassettes/se/save-create-known-id.yml +146 -0
  72. data/spec/vcr_cassettes/se/save-create-unknown-id.yml +56 -0
  73. data/spec/vcr_cassettes/se/save-invalid.yml +305 -0
  74. data/spec/vcr_cassettes/se/save-update-valid.yml +99 -0
  75. data/spec/vcr_cassettes/se/search-malformed-uuid.yml +54 -0
  76. data/spec/vcr_cassettes/se/search-with-existing-se.yml +52 -0
  77. data/spec/vcr_cassettes/se/search-with-missing-se.yml +52 -0
  78. data/spec/vcr_cassettes/search/search-with-existing-se.yml +52 -0
  79. data/spec/vcr_cassettes/search_results/no-results.yml +52 -0
  80. data/spec/vcr_cassettes/search_results/search-by-digi_id.yml +101 -0
  81. data/spec/vcr_cassettes/search_results/search-by-step.yml +201 -0
  82. metadata +307 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9782c78a09597d6fee30ab7c6853c4a2c5ddce3d
4
+ data.tar.gz: 87adba5bdde063e8951e53499224037420ff4c12
5
+ SHA512:
6
+ metadata.gz: b6769297879400a946220562a7e5a9fe1bb360773b1618246c54e7f9860dce6aab4bfb0bbab6cde1ae07c3b0e1940fa09d68a04961c2ecaff99e0984b400a0fc
7
+ data.tar.gz: ae2f8b4c95a096dc8d1a975fc7581efa23e42f4afd5dbdbeb7a4b325ddb869e4d79428988dec2a39c906d5b0975c10ef30d7480ec92b27cd166171339cbaed1d
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .ruby-version
19
+ .ruby-gemset
20
+ *~
21
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
@@ -0,0 +1,17 @@
1
+ ## Getting Started
2
+
3
+ The following instructions assume that you have [`RVM` installed](https://rvm.io/rvm/install).
4
+
5
+ ```
6
+ git clone git@github.com:NYULibraries/rsbe-client.git
7
+ cd rsbe-client
8
+ rvm install 2.3
9
+ rvm use 2.3
10
+ rvm --ruby-version gemset use rsbe-client --create
11
+ gem install bundler
12
+ bundle
13
+ rake
14
+ ```
15
+
16
+
17
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rsbe-client.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 jgpawletko
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,73 @@
1
+ # Rsbe::Client
2
+
3
+ Client library for interaction with the rsbe API
4
+
5
+ ## Current Status
6
+
7
+ ### *UNDER DEVELOPMENT*
8
+
9
+ ## Description
10
+
11
+ This library allows one to interact with the R* Backend (rsbe) API.
12
+
13
+ #### Completed User stories:
14
+ * As an Authorized Client, I want to create a new Partner and have it persist.
15
+ * As an Authorized Client, I want to update a Partner.
16
+ * As an Authorized Client, I want to find a Partner by id.
17
+ * As an Authorized Client, I want to get a list of all available Partners.
18
+
19
+
20
+ #### Required Environment Variables:
21
+ ```
22
+ RSBE_URL
23
+ RSBE_USER
24
+ RSBE_PASSWORD
25
+ ```
26
+
27
+ e.g.,
28
+ ```
29
+ export RSBE_URL='https://rsbe.example.com'
30
+ export RSBE_USER='foo'
31
+ export RSBE_PASSWORD='bar'
32
+ ```
33
+
34
+ #### Usage (irb example from project root)
35
+ ```
36
+ $ irb -I lib
37
+ irb> require 'rsbe/client'
38
+ => true
39
+ irb> include Rsbe::Client
40
+ => Object
41
+
42
+ irb> Partner.all.each {|p| puts p.id}
43
+ 51213be7-c8de-4e06-8cc2-06bfc82cdd68
44
+ 977e659b-886a-4626-8799-8979426ad2b3
45
+ ...
46
+ ```
47
+
48
+ #### Usage (code example)
49
+ ```
50
+ ...
51
+ def select_item_or_exit(items, label)
52
+ # do some stuff
53
+ end
54
+ ...
55
+
56
+ partners = Rsbe::Client::Partner.all
57
+ partners.sort! { |a, b| a.code <=> b.code }
58
+ partner = select_item_or_exit(partners, 'partner')
59
+
60
+ collections = partner.collections.sort { |a, b| a.code <=> b.code }
61
+ collection = select_item_or_exit(collections, 'collection')
62
+ puts "collection: #{collection.code}, #{collection.name} #{collection.id}"
63
+ ...
64
+ ```
65
+
66
+ #### Methods:
67
+ ```
68
+ Rsbe::Partner#save
69
+ Rsbe::Partner#code
70
+ Rsbe::Partner#name
71
+ Rsbe::Partner#collections
72
+ ```
73
+
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
9
+ rescue LoadError
10
+ # no rspec available
11
+ end
data/TODO.md ADDED
@@ -0,0 +1,38 @@
1
+ ## TODO
2
+ [ ] consider creating request and response classes that handle 404, 201, 200, etc.
3
+ [ ] consider base class with get/put/post/delete methods that can be overridden, or better yet, create a "Connection" class
4
+ which would be useful for class-level operations as well. Then objects would have a @conn instance variable.
5
+
6
+ #### Pending User stories:
7
+ * As an Authorized Client, I want to find a Partner by code.
8
+ * As an Authorized Client, I want to list the Colls for a Partner.
9
+
10
+ * As an Authorized Client, I want to create a new Coll and have it persist.
11
+
12
+ * As an Authorized Client, I want to create a new FMD.
13
+ * As an Authorized Client, I want to use a checksum to find a list of all matching files with that same checksum.
14
+ * As an Authorized Client, I want to update the last ping datetime of an FMD.
15
+ * As an Authorized Client, I want to add a checksum value to an FMD.
16
+ * As an Authorized Client, I want to update the last fixity check datetime of an FMD.
17
+ * As an Authorized Client, I want to update the path of an FMD.
18
+ * As an Authorized Client, I want to find the id of an Fmd given its path.
19
+
20
+
21
+ #### Possible Usage
22
+ ```ruby
23
+ p = Rsbe::Client::Partner.new(attrs)
24
+ p.save # => true or false
25
+ ```
26
+
27
+ ```ruby
28
+ p = Rsbe::Client::Partner.find_by_code(code)
29
+ p.name = "Lorem Ipsum"
30
+ p.save
31
+ c = p.collections.new(attrs)
32
+ c.quota = 9999
33
+ c.ready_for_content = true
34
+ unless c.save
35
+ puts c.errors
36
+ end
37
+ ```
38
+
@@ -0,0 +1,50 @@
1
+ #CLIENT_CLASS = %w()
2
+ %w(base
3
+ partner
4
+ collection
5
+ provider
6
+ se
7
+ connection not_found_error
8
+ method_not_implemented_error
9
+ wrong_origin_error
10
+ unrecognized_resource_error
11
+ search
12
+ search_results).each do |r|
13
+ require_relative "./client/#{r}"
14
+ end
15
+
16
+ module Rsbe
17
+ module Client
18
+ def self.find(url)
19
+ assert_same_origin(url)
20
+ find_and_instantiate(url)
21
+ end
22
+
23
+ def self.assert_same_origin(url)
24
+ conn = Rsbe::Client::Connection.new
25
+ unless conn.same_origin?(url)
26
+ raise Rsbe::Client::WrongOriginError.new("incorrect origin: #{url}")
27
+ end
28
+ end
29
+ private_class_method :assert_same_origin
30
+
31
+ def self.find_and_instantiate(url)
32
+ retval = nil
33
+ [
34
+ Rsbe::Client::Partner,
35
+ Rsbe::Client::Provider,
36
+ Rsbe::Client::Collection,
37
+ Rsbe::Client::Se
38
+ ].each do |klass|
39
+ # UUID Regexp from http://stackoverflow.com/a/14166194
40
+ m = /#{klass.base_path}\/(?<uuid>[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89aAbB][a-f0-9]{3}-[a-f0-9]{12})\z/i.match(url)
41
+ next if m.nil?
42
+ retval = klass.find(m['uuid'])
43
+ break
44
+ end
45
+ raise Rsbe::Client::UnrecognizedResourceError.new("no matching resource found for #{url}") if retval.nil?
46
+ retval
47
+ end
48
+ private_class_method :find_and_instantiate
49
+ end
50
+ end
@@ -0,0 +1,140 @@
1
+ require 'faraday'
2
+ require 'json'
3
+ module Rsbe
4
+ module Client
5
+ class Base
6
+ def self.find(id)
7
+ o = find_and_instantiate(id)
8
+ raise Rsbe::Client::RecordNotFound.new("#{self.name} with #{id} not found") if o.nil?
9
+ o
10
+ end
11
+
12
+ # returns an array of Partner objects
13
+ def self.all
14
+ conn = Rsbe::Client::Connection.new
15
+ local_response = conn.get base_path
16
+ raise "Error retrieving partners" unless local_response.status == 200
17
+ JSON.parse(local_response.body).collect { |json_hash| new(json_hash) }
18
+ end
19
+
20
+ def self.find_and_instantiate(id)
21
+ p = self.new(id: id)
22
+ if p.send(:get)
23
+ p.send(:update_hash_from_response)
24
+ else
25
+ p = nil
26
+ end
27
+ p
28
+ end
29
+ private_class_method :find_and_instantiate
30
+
31
+ def initialize
32
+ @conn = Rsbe::Client::Connection.new
33
+ end
34
+ def self.base_path
35
+ '/api/v0'
36
+ end
37
+ def save
38
+ (has_id? && exists?) ? update : create
39
+ end
40
+
41
+ private
42
+ # N.B. intentionally not updating hash here
43
+ def get(id = @hash[:id])
44
+ @response = @conn.get item_path(id)
45
+ @response.status == 200
46
+ end
47
+
48
+ def get_children(path_segment)
49
+ path = item_path(id) + '/' + path_segment
50
+ @response = @conn.get path
51
+ @response.status == 200
52
+ end
53
+
54
+ def create
55
+ @response = @conn.post do |req|
56
+ req.url coll_path
57
+ req.headers['Content-Type'] = 'application/json'
58
+ req.body = @hash.to_json
59
+ end
60
+ return false unless @response.status == 201
61
+
62
+ update_hash_from_response
63
+ true
64
+ end
65
+
66
+ def update_hash_from_response
67
+ raise "@response not initialized" if @response.nil?
68
+
69
+ # update attributes with those from server
70
+ response_hash = JSON.parse(@response.body)
71
+ raise "unable to parse response to hash" unless response_hash.is_a?(Hash)
72
+ update_hash(response_hash)
73
+ end
74
+
75
+ def update_hash_nils_from_response
76
+ raise "@response not initialized" if @response.nil?
77
+
78
+ # update attributes with those from server
79
+ response_hash = JSON.parse(@response.body)
80
+ raise "unable to parse response to hash" unless response_hash.is_a?(Hash)
81
+ update_hash_nils(response_hash)
82
+ end
83
+
84
+ def update_hash(arg)
85
+ # update object state
86
+ self.class.all_attrs.each { |k| (@hash[k] = arg[k.to_s]) if arg.has_key?(k.to_s) }
87
+ end
88
+
89
+ def update_hash_nils(arg)
90
+ # update object state
91
+ self.class.all_attrs.each do |k|
92
+ (@hash[k] = arg[k.to_s]) if @hash[k].nil?
93
+ end
94
+ end
95
+
96
+ def update
97
+ @response = @conn.put do |req|
98
+ req.url item_path(@hash[:id])
99
+ req.headers['Content-Type'] = 'application/json'
100
+ req.body = @hash.to_json
101
+ end
102
+
103
+ success = @response.status == 204
104
+ if success
105
+ get
106
+ update_hash_from_response
107
+ end
108
+ success
109
+ end
110
+
111
+ def has_id?
112
+ !@hash[:id].nil?
113
+ end
114
+
115
+ def exists?
116
+ get
117
+ end
118
+
119
+ def coll_path
120
+ self.class.base_path
121
+ end
122
+
123
+ def item_path(id)
124
+ coll_path + "/#{id}"
125
+ end
126
+
127
+ def self.rw_attrs
128
+ []
129
+ end
130
+
131
+ def self.ro_attrs
132
+ []
133
+ end
134
+
135
+ def self.all_attrs
136
+ self.rw_attrs + self.ro_attrs
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,84 @@
1
+ require 'active_support'
2
+
3
+ module Rsbe
4
+ module Client
5
+ # Class simplifies interaction with RSBE Partner resources
6
+ class Collection < Base
7
+ def self.all
8
+ emsg = 'Method not supported. Access via Rsbe::Client::Partner#collections'
9
+ fail Rsbe::Client::MethodNotImplementedError, emsg
10
+ end
11
+
12
+ def self.base_path
13
+ super + '/colls'
14
+ end
15
+
16
+ def self.rw_attrs
17
+ [:id, :code, :partner_id, :coll_type, :quota, :name, :rel_path]
18
+ end
19
+
20
+ def self.ro_attrs
21
+ [:created_at, :updated_at, :lock_version]
22
+ end
23
+
24
+ def self.all_attrs
25
+ rw_attrs + ro_attrs
26
+ end
27
+
28
+ # define setter methods for RW attributes
29
+ rw_attrs.each { |m| define_method("#{m}=") { |v| @hash[m] = v } }
30
+
31
+ # define getter methods for ALL attributes
32
+ all_attrs.each do |m|
33
+ define_method("#{m}") do
34
+ @hash[m] || begin
35
+ # only fetch if Partner has a known id
36
+ if @hash[:id]
37
+ # TODO: CLEAN THIS UP
38
+ # This works currently because when a 404 is
39
+ # returned there is nothing in the response
40
+ # hash that matches the resource attributes,
41
+ # but relying on this behavior seems very
42
+ # brittle. Additionally, for every attribute
43
+ # queried, a GET operation is performed.
44
+ # Should store the object lifecycle state,
45
+ # e.g.,
46
+ # - new, no ID, no matching resource in RSBE: don't query RSBE
47
+ # - new, ID assigned, but not yet in RSBE: don't query RSBE
48
+ # - existing, partially populated,
49
+ # not fully updated with RSBE values: query RSBE
50
+ # - existing, fully populated with data from RSBE
51
+ # - existing, local modifications, not persisted to RSBE
52
+ #
53
+ get
54
+ update_hash_nils_from_response
55
+ end
56
+ @hash[m]
57
+ end
58
+ end
59
+ end
60
+
61
+ def initialize(vals = {})
62
+ fail(ArgumentError, 'Constructor requires a Hash') unless vals.is_a?(Hash)
63
+ super()
64
+ @hash = {}
65
+ @response = nil
66
+
67
+ # initialize local hash with incoming values, restrict to RW attrs
68
+ self.class.rw_attrs.each { |x| @hash[x] = (vals[x] || vals[x.to_s]) }
69
+ end
70
+
71
+ def create_path
72
+ fail 'partner_id not initialized!' unless partner_id
73
+ Rsbe::Client::Partner.item_path(partner_id)
74
+ end
75
+
76
+ def ses
77
+ fail 'Error getting source entities' unless get_children('ses')
78
+ JSON.parse(@response.body).collect do |json_hash|
79
+ Rsbe::Client::Se.new(json_hash)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end