ed_fi_client 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +78 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +5 -0
  7. data/.yardopts +1 -0
  8. data/Gemfile +6 -0
  9. data/Gemfile.lock +65 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +113 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +8 -0
  15. data/docs/EdFi.html +127 -0
  16. data/docs/EdFi/Client.html +1204 -0
  17. data/docs/EdFi/Client/AccessToken.html +541 -0
  18. data/docs/EdFi/Client/ArgumentError.html +143 -0
  19. data/docs/EdFi/Client/Auth.html +440 -0
  20. data/docs/EdFi/Client/Error.html +139 -0
  21. data/docs/EdFi/Client/Proxy.html +521 -0
  22. data/docs/EdFi/Client/Response.html +479 -0
  23. data/docs/EdFi/Client/UnableToAuthenticateError.html +145 -0
  24. data/docs/_index.html +203 -0
  25. data/docs/class_list.html +51 -0
  26. data/docs/css/common.css +1 -0
  27. data/docs/css/full_list.css +58 -0
  28. data/docs/css/style.css +499 -0
  29. data/docs/file.README.html +124 -0
  30. data/docs/file_list.html +56 -0
  31. data/docs/frames.html +17 -0
  32. data/docs/index.html +124 -0
  33. data/docs/js/app.js +248 -0
  34. data/docs/js/full_list.js +216 -0
  35. data/docs/js/jquery.js +4 -0
  36. data/docs/method_list.html +235 -0
  37. data/docs/top-level-namespace.html +110 -0
  38. data/ed_fi_client.gemspec +32 -0
  39. data/lib/ed_fi/client.rb +244 -0
  40. data/lib/ed_fi/client/access_token.rb +63 -0
  41. data/lib/ed_fi/client/auth.rb +99 -0
  42. data/lib/ed_fi/client/errors.rb +19 -0
  43. data/lib/ed_fi/client/proxy.rb +65 -0
  44. data/lib/ed_fi/client/response.rb +150 -0
  45. data/lib/ed_fi/client/version.rb +18 -0
  46. data/lib/ed_fi_client.rb +1 -0
  47. metadata +201 -0
@@ -0,0 +1,99 @@
1
+ require 'crapi'
2
+ require 'json'
3
+
4
+ require 'ed_fi/client/access_token'
5
+ require 'ed_fi/client/errors'
6
+
7
+ class EdFi::Client < Crapi::Client
8
+ ## The {EdFi::Client::Auth EdFi::Client::Auth} represents a complete authentication *mechanism*
9
+ ## that makes the necessary calls for authorization codes and access tokens, keeps track of any
10
+ ## token generated, and re-requests new tokens as necessary based on the existing token's
11
+ ## lifecycle.
12
+ ##
13
+ ##
14
+ class Auth
15
+ ## The URI to request an authorization code.
16
+ AUTHORIZATION_CODE_URI = '/oauth/authorize'.freeze
17
+
18
+ ## The MIME content type to use for authorization code requests.
19
+ AUTHORIZATION_CODE_CONTENT_TYPE = 'application/x-www-form-urlencoded'.freeze
20
+
21
+ ## The URI to request an access token.
22
+ ACCESS_TOKEN_URI = '/oauth/token'.freeze
23
+
24
+ ## The MIME content type to use for access token requests.
25
+ ACCESS_TOKEN_CONTENT_TYPE = 'application/json'.freeze
26
+
27
+ ## @param client [Crapi::Client]
28
+ ## The client to use for making auth calls.
29
+ ##
30
+ ## @param client_id [String]
31
+ ## The client id to use for authentication. This is AKA the "api key" / "username".
32
+ ##
33
+ ## @param client_secret [String]
34
+ ## The client secret to use for authentication. This is AKA the "api secret" / "password".
35
+ ##
36
+ def initialize(client:, client_id:, client_secret:)
37
+ @client = client
38
+ @client_id = client_id
39
+ @client_secret = client_secret
40
+
41
+ @access_token = nil
42
+ end
43
+
44
+ ## Gives an access token string that is guaranteed to be valid for *at least* 5 seconds.
45
+ ##
46
+ ## Note a new token is requested and returned if the existing token is no longer valid.
47
+ ##
48
+ ##
49
+ ## @return [String]
50
+ ##
51
+ def token
52
+ @access_token = new_access_token unless @access_token&.valid?
53
+ @access_token.token
54
+ end
55
+
56
+ ##
57
+
58
+ private
59
+
60
+ ##
61
+
62
+ ## Requests and yields a new authorization *code".
63
+ ##
64
+ ##
65
+ ## @raise [EdFi::Client::UnableToAuthenticateError]
66
+ ##
67
+ ## @return [String]
68
+ ##
69
+ def new_authorization_code
70
+ auth = @client.post(
71
+ AUTHORIZATION_CODE_URI,
72
+ payload: { Client_id: @client_id, Response_type: 'code' },
73
+ headers: { 'Content-Type': AUTHORIZATION_CODE_CONTENT_TYPE }
74
+ )
75
+ raise EdFi::Client::UnableToAuthenticateError, 'Failed to fetch authorization code.' unless auth&.key? :code
76
+
77
+ auth[:code]
78
+ end
79
+
80
+ ## Requests and yields a new access *token*.
81
+ ##
82
+ ##
83
+ ## @raise [EdFi::Client::UnableToAuthenticateError]
84
+ ##
85
+ ## @return [EdFi::Client::AccessToken]
86
+ ##
87
+ def new_access_token
88
+ auth = @client.post(
89
+ ACCESS_TOKEN_URI,
90
+ payload: { Client_id: @client_id, Client_secret: @client_secret,
91
+ Code: new_authorization_code, Grant_type: 'authorization_code' },
92
+ headers: { 'Content-Type': ACCESS_TOKEN_CONTENT_TYPE }
93
+ )
94
+ raise EdFi::Client::UnableToAuthenticateError, 'Failed to fetch access token.' unless auth&.key? :access_token
95
+
96
+ EdFi::Client::AccessToken.new(auth)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,19 @@
1
+ require 'crapi'
2
+
3
+ class EdFi::Client < Crapi::Client
4
+ ## The base Error class for all {EdFi::Client}-related issues.
5
+ ##
6
+ class Error < ::StandardError
7
+ end
8
+
9
+ ## An error relating to missing, invalid, or incompatible method arguments.
10
+ ##
11
+ class ArgumentError < Error
12
+ end
13
+
14
+ ## An error relating to a bad request for an authorization *code* or access *token*. This is most
15
+ ## likely due to a connectivity issue, a bad base URI in the given client, or invalid credentials.
16
+ ##
17
+ class UnableToAuthenticateError < Error
18
+ end
19
+ end
@@ -0,0 +1,65 @@
1
+ require 'crapi'
2
+
3
+ class EdFi::Client < Crapi::Client
4
+ ## The Crapi::Proxy to {EdFi::Client}'s Crapi::Client.
5
+ ##
6
+ ## An {EdFi::Client::Proxy EdFi::Client::Proxy} calls {EdFi::Client::Response#client= #client=}
7
+ ## on every CRUD-method-generated {EdFi::Client::Response EdFi::Client::Response}.
8
+ ##
9
+ class Proxy < Crapi::Proxy
10
+ ## CRUD method: GET
11
+ ##
12
+ ## All parameters are passed directly through to Crapi::Proxy#get.
13
+ ##
14
+ def get(*args)
15
+ response = super
16
+ response.client = self
17
+
18
+ response
19
+ end
20
+
21
+ ## CRUD method: DELETE
22
+ ##
23
+ ## All parameters are passed directly through to Crapi::Proxy#delete.
24
+ ##
25
+ def delete(*args)
26
+ response = super
27
+ response.client = self
28
+
29
+ response
30
+ end
31
+
32
+ ## CRUD method: POST
33
+ ##
34
+ ## All parameters are passed directly through to Crapi::Proxy#post.
35
+ ##
36
+ def post(*args)
37
+ response = super
38
+ response.client = self
39
+
40
+ response
41
+ end
42
+
43
+ ## CRUD method: PATCH
44
+ ##
45
+ ## All parameters are passed directly through to Crapi::Proxy#patch.
46
+ ##
47
+ def patch(*args)
48
+ response = super
49
+ response.client = self
50
+
51
+ response
52
+ end
53
+
54
+ ## CRUD method: PUT
55
+ ##
56
+ ## All parameters are passed directly through to Crapi::Proxy#put.
57
+ ##
58
+ def put(*args)
59
+ response = super
60
+ response.client = self
61
+
62
+ response
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,150 @@
1
+ require 'crapi'
2
+
3
+ class EdFi::Client < Crapi::Client
4
+ ## Represents an API response. {EdFi::Client::Response EdFi::Client::Response} instances
5
+ ## initialized from a Hash also allow for reference chaining.
6
+ ##
7
+ class Response
8
+ ## @param response [Hash, Array]
9
+ ## The value to encapsulate as an {EdFi::Client::Response EdFi::Client::Response}.
10
+ ##
11
+ ## @param client [Crapi::Client]
12
+ ## The client to use for request chaining.
13
+ ##
14
+ ##
15
+ ## @raise [EdFi::Client::ArgumentError]
16
+ ##
17
+ def initialize(response, client: nil)
18
+ @client = client
19
+
20
+ case response
21
+ when Hash
22
+ @response = response.to_a.map do |tuple|
23
+ (key, value) = tuple.dup
24
+ key = key.to_s.underscore.to_sym
25
+ value = EdFi::Client::Response.new(value, client: @client) if value.is_a?(Hash) || value.is_a?(Array)
26
+
27
+ [key, value]
28
+ end.to_h.with_indifferent_access
29
+
30
+ when Array
31
+ @response = response.dup.map do |i|
32
+ i = EdFi::Client::Response.new(i, client: @client) if i.is_a?(Hash) || i.is_a?(Array)
33
+ i
34
+ end
35
+
36
+ when nil, ''
37
+ ## This can happen for non-body-returning calls.
38
+ @response = {}.with_indifferent_access
39
+
40
+ else
41
+ raise EdFi::Client::ArgumentError, %(Unexpected "response" type: #{response.class})
42
+
43
+ end
44
+ end
45
+
46
+ ## Deep updates the associated Crapi::Client] for this and all descendant
47
+ ## {EdFi::Client::Response EdFi::Client::Response} instances.
48
+ ##
49
+ def client=(client)
50
+ @client = client
51
+
52
+ case @response
53
+ when Hash
54
+ @response.values.each { |i| i.client = client if i.is_a? EdFi::Client::Response }
55
+
56
+ when Array
57
+ @response.each { |i| i.client = client if i.is_a? EdFi::Client::Response }
58
+
59
+ end
60
+ end
61
+
62
+ ## @private
63
+ ##
64
+ def to_s
65
+ @response.to_s
66
+ end
67
+
68
+ ## @private
69
+ ##
70
+ def inspect
71
+ @response.inspect
72
+ end
73
+
74
+ ## rubocop:disable Security/Eval
75
+ ##
76
+ ## We're running `eval` on the `#to_s` of a built-in type, which is safe.
77
+ ## Attempting to let `#as_json` run on its own results in a stack overflow.
78
+
79
+ ## @private
80
+ ##
81
+ def as_json
82
+ eval(to_s).as_json
83
+ end
84
+ ## rubocop:enable Security/Eval
85
+
86
+ ## rubocop:disable Security/Eval
87
+ ##
88
+ ## We're running `eval` on the `#to_s` of a built-in type, which is safe.
89
+ ## Attempting to let `#to_json` run on its own results in a stack overflow.
90
+
91
+ ## @private
92
+ ##
93
+ def to_json
94
+ eval(to_s).to_json
95
+ end
96
+ ## rubocop:enable Security/Eval
97
+
98
+ ## rubocop:disable Style/MethodMissing, Metrics/BlockNesting, Metrics/PerceivedComplexity
99
+
100
+ ## @private
101
+ ##
102
+ def method_missing(name, *args, &block)
103
+ ## Note references are cached.
104
+ ## To force a refresh on an already-cached reference,
105
+ ## the method should be called with a single `true` parameter.
106
+ ## (i.e. `#school` vs `#school(true)`)
107
+
108
+ if @response.is_a? Hash
109
+ ## Allow for acceess to response values via dot notation.
110
+ return @response[name] if @response.key? name
111
+
112
+ ## Allow for assignment to response values via dot notation.
113
+ return @response.send(:'[]=', name[0...-1], *args, &block) if name.to_s.ends_with? '='
114
+
115
+ ## Allow for simple access to referenced resources.
116
+ if @client.present?
117
+ @references ||= {}
118
+ reference = @response["#{name}_reference"].link.href rescue nil
119
+
120
+ if reference.present?
121
+ @references.delete(reference) if args[0] == true
122
+ return @references[reference] ||= @client.get(reference)
123
+ end
124
+ end
125
+ end
126
+
127
+ ## All other unaccounted-for method calls should be delegated to the response Hash/Array.
128
+ @response.send(name, *args, &block)
129
+ end
130
+ ## rubocop:enable Style/MethodMissing, Metrics/BlockNesting, Metrics/PerceivedComplexity
131
+
132
+ ## @private
133
+ ##
134
+ def respond_to_missing?(name, include_private = false)
135
+ ( \
136
+ ( \
137
+ (@response.is_a? Hash) \
138
+ && \
139
+ ( \
140
+ @response.key?(name) \
141
+ || \
142
+ @response.key?("#{name}_reference".to_sym) \
143
+ ) \
144
+ ) \
145
+ || \
146
+ @response.respond_to?(name, include_private) \
147
+ )
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,18 @@
1
+ require 'crapi'
2
+
3
+ ## Note we're explicitly declaring "EdFi" as a module here so this file can be directly require'd
4
+ ## from the base gemspec without issues.
5
+
6
+ ## The EdFi module houses the {EdFi::Client EdFi::Client} in this gem, but should also house future
7
+ ## EdFi tooling.
8
+ ##
9
+ module EdFi
10
+ class Client < Crapi::Client
11
+ ## The canonical **ed_fi_client** gem version.
12
+ ##
13
+ ## This should only ever be updated *immediately* before a release; the commit that updates this
14
+ ## value should be pushed **by** the `rake release` process.
15
+ ##
16
+ VERSION = '0.1.0'.freeze
17
+ end
18
+ end
@@ -0,0 +1 @@
1
+ require 'ed_fi/client'
metadata ADDED
@@ -0,0 +1,201 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ed_fi_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nestor Custodio
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-05-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.11'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.11'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec_junit_formatter
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.3'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: activesupport
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 5.2.0
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 5.2.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: crapi
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.1'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.1'
125
+ description:
126
+ email:
127
+ - sakimorix@gmail.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - ".gitignore"
133
+ - ".rspec"
134
+ - ".rubocop.yml"
135
+ - ".ruby-version"
136
+ - ".travis.yml"
137
+ - ".yardopts"
138
+ - Gemfile
139
+ - Gemfile.lock
140
+ - LICENSE.txt
141
+ - README.md
142
+ - Rakefile
143
+ - bin/console
144
+ - bin/setup
145
+ - docs/EdFi.html
146
+ - docs/EdFi/Client.html
147
+ - docs/EdFi/Client/AccessToken.html
148
+ - docs/EdFi/Client/ArgumentError.html
149
+ - docs/EdFi/Client/Auth.html
150
+ - docs/EdFi/Client/Error.html
151
+ - docs/EdFi/Client/Proxy.html
152
+ - docs/EdFi/Client/Response.html
153
+ - docs/EdFi/Client/UnableToAuthenticateError.html
154
+ - docs/_index.html
155
+ - docs/class_list.html
156
+ - docs/css/common.css
157
+ - docs/css/full_list.css
158
+ - docs/css/style.css
159
+ - docs/file.README.html
160
+ - docs/file_list.html
161
+ - docs/frames.html
162
+ - docs/index.html
163
+ - docs/js/app.js
164
+ - docs/js/full_list.js
165
+ - docs/js/jquery.js
166
+ - docs/method_list.html
167
+ - docs/top-level-namespace.html
168
+ - ed_fi_client.gemspec
169
+ - lib/ed_fi/client.rb
170
+ - lib/ed_fi/client/access_token.rb
171
+ - lib/ed_fi/client/auth.rb
172
+ - lib/ed_fi/client/errors.rb
173
+ - lib/ed_fi/client/proxy.rb
174
+ - lib/ed_fi/client/response.rb
175
+ - lib/ed_fi/client/version.rb
176
+ - lib/ed_fi_client.rb
177
+ homepage: https://www.github.com/nestor-custodio/ed_fi_client
178
+ licenses:
179
+ - MIT
180
+ metadata: {}
181
+ post_install_message:
182
+ rdoc_options: []
183
+ require_paths:
184
+ - lib
185
+ required_ruby_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ required_rubygems_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ requirements: []
196
+ rubyforge_project:
197
+ rubygems_version: 2.7.6
198
+ signing_key:
199
+ specification_version: 4
200
+ summary: A simple API wrapper for Ed-Fi ODS access.
201
+ test_files: []