sentofu 0.0.1 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32c5cc4788ad456cd8121cb497ec1edd2646414cc2006466d18cef63c14d3d12
4
- data.tar.gz: d681e4a0b6c3d95a3ad2bf6e3a50da249c3876503fb1cd057bd5e94fe94f2e58
3
+ metadata.gz: ec10af5cec1acf4c593f766637358a1cc323665bf65cde68c5c727c637582f7f
4
+ data.tar.gz: e75a6edec73c0c0faa5cc7e9a8657f9d348a25bbcc023d3422b4f62dd9e89a5e
5
5
  SHA512:
6
- metadata.gz: 67f28338c633693c0d2bff8157beab313be3d9a07c6b9ecacd7652710ff3282d3fac42a11f3e6e1973374486f1fb6fa13d4ce1472ca958ae2e2fb3cb3a683823
7
- data.tar.gz: e895b60701e76e2f2a50791b3727caa611d6da06bc84e05ef7be7c222531994b25ad3ae48c9c94d38eddfcf2d2a00124ab734166f9ad1bc49d3998c42d05b006
6
+ metadata.gz: 7e8145180ed891775ef9cf4358778091d18fe5f4199cf2c87a899de9ec14e5ab3a60d18cfa93314a3417660a3a2cb34757bedcaefc0d6b51f5da862659f9836a
7
+ data.tar.gz: 2f3a1bf05a54b680bdf6247ea2c9b72cac64a590586341476d9b90fe2c2b27986bbcd32d55f94e190fda85d13ffca4daa5a005930abc50e3f2855ea642b44ee0
data/CHANGELOG.md CHANGED
@@ -2,7 +2,12 @@
2
2
  # CHANGELOG.md
3
3
 
4
4
 
5
+ ## sentofu 0.1.0 released 2019-05-23
6
+
7
+ * Initial implementation
8
+
9
+
5
10
  ## sentofu 0.0.1 released 2019-05-19
6
11
 
7
- * initial empty release
12
+ * Initial empty release
8
13
 
data/Makefile CHANGED
@@ -40,5 +40,15 @@ test: spec
40
40
 
41
41
  ## specific to project ##
42
42
 
43
- .PHONY: count_lines scan gemspec_validate name cw build push spec
43
+ detail_apis:
44
+ time bundle exec ruby -I lib -r yaml -r sentofu -e "puts YAML.dump(Sentofu.detail_apis)"
45
+ list_apis:
46
+ time bundle exec ruby -I lib -r yaml -r sentofu -e "puts YAML.dump(Sentofu.list_apis)"
47
+ dump_apis:
48
+ time bundle exec ruby -I lib -r sentofu -e "Sentofu.dump_apis"
49
+
50
+
51
+ ## done ##
52
+
53
+ .PHONY: count_lines scan gemspec_validate name cw build push spec detail_apis list_apis dump_apis
44
54
 
data/lib/sentofu.rb CHANGED
@@ -1,21 +1,112 @@
1
1
 
2
2
  require 'json'
3
3
  require 'yaml'
4
+ require 'time'
4
5
  require 'base64'
5
6
  require 'ostruct'
6
7
  require 'net/http'
7
8
 
8
9
  require 'sentofu/http'
9
10
  require 'sentofu/api'
11
+ require 'sentofu/explo'
10
12
 
11
13
 
12
14
  module Sentofu
13
15
 
14
- VERSION = '0.0.1'
16
+ VERSION = '0.1.0'
15
17
 
16
- Dir[File.join(__dir__, 'sentofu/sentifi-*.yaml')].each do |fpath|
18
+ USER_AGENT =
19
+ "Sentofu #{Sentofu::VERSION} - " +
20
+ [ 'Ruby', RUBY_VERSION, RUBY_RELEASE_DATE, RUBY_PLATFORM ].join(' ')
21
+ @user_agent =
22
+ USER_AGENT
17
23
 
18
- Sentofu::Api.make(YAML.load(File.read(fpath)))
24
+ @auth_uri = nil
25
+ @apis = {}
26
+
27
+ class << self
28
+
29
+ attr_reader :auth_uri, :apis
30
+ attr_accessor :user_agent
31
+
32
+ def init(versions=%w[ common:1.0.0 company:1.0.0 markets:1.0.0 ])
33
+
34
+ vers = split_versions(versions)
35
+
36
+ vers << %w[ auth * ] unless vers.find { |n, _| n == 'auth' }
37
+
38
+ vers.each do |api_name, ver_pattern|
39
+
40
+ doc_uri =
41
+ 'https://api.swaggerhub.com/apis/sentifi-api-docs' +
42
+ (api_name == 'auth' ?
43
+ '/sentifi-api_o_auth_2_authentication_and_authorization/' :
44
+ "/sentifi-intelligence_#{api_name}_api/")
45
+
46
+ metas = Sentofu::Http.get_and_parse(doc_uri)
47
+
48
+ v, u, meta = metas['apis']
49
+ .collect { |m|
50
+ prs = m['properties']
51
+ [ prs.find { |pr| pr['type'] == 'X-Version' }['value'],
52
+ prs.find { |pr| pr['type'] == 'Swagger' }['url'],
53
+ m ] }
54
+ .select { |v, _, _| version_match(v, ver_pattern) }
55
+ .sort_by(&:first)
56
+ .first
57
+
58
+ spec = Sentofu::Http.get_and_parse(u)
59
+ spec[:meta] = meta
60
+
61
+ if api_name == 'auth'
62
+ @auth_uri = spec['servers'][0]['url'] + spec['paths'].keys.first
63
+ else
64
+ api = Sentofu::Api.new(api_name, spec)
65
+ Sentofu.define_singleton_method(api_name) { api }
66
+ @apis[api_name] = api
67
+ end
68
+ end
69
+ end
70
+
71
+ def credentials=(cs)
72
+
73
+ apis.each { |_, api| api.credentials = cs }
74
+ end
75
+
76
+ protected
77
+
78
+ def split_versions(vs)
79
+
80
+ case vs
81
+ when Array
82
+ vs
83
+ .select { |v| v.strip.length > 0 }
84
+ .collect { |v| split_version(v) }
85
+ when String
86
+ vs.split(/[,;]/)
87
+ .select { |v| v.strip.length > 0 }
88
+ .collect { |v| split_version(v) }
89
+ else
90
+ vs
91
+ end
92
+ end
93
+
94
+ def split_version(v)
95
+
96
+ v.is_a?(Array) ? v : v.split(':').collect(&:strip)
97
+ end
98
+
99
+ def version_match(version, pattern)
100
+
101
+ ves = version.split('.')
102
+ pattern.split('.').each do |pa|
103
+ ve = ves.shift
104
+ next if pa == 'x' || pa == '*'
105
+ return false if ve != pa
106
+ end
107
+
108
+ true
109
+ end
19
110
  end
20
111
  end
21
112
 
@@ -31,7 +122,13 @@ if $0 == __FILE__
31
122
  #pp y['paths'].keys
32
123
 
33
124
  #p Sentofu.company.class
34
- p Sentofu.company.topic_search
125
+ #p Sentofu.company.topic_search
35
126
  #pp Sentofu.markets
127
+
128
+ #puts Sentofu.company.paths.keys
129
+
130
+ t0 = Time.now
131
+ Sentofu.init
132
+ puts "took #{Time.now - t0}s..."
36
133
  end
37
134
 
data/lib/sentofu/api.rb CHANGED
@@ -3,6 +3,8 @@ module Sentofu
3
3
 
4
4
  class Resource
5
5
 
6
+ TVP = '_sentofu_' # THREAD VARIABLE PREFIX
7
+
6
8
  attr_reader :parent, :segment
7
9
 
8
10
  def initialize(parent, segment)
@@ -14,20 +16,22 @@ module Sentofu
14
16
 
15
17
  def add_segment(segment)
16
18
 
17
- m = segment.match(/\A\{([^}]+)\}\z/)
19
+ m = segment.match(/\A\{[^}]+\}\z/)
18
20
  mth = m ? :[] : segment
19
21
 
20
22
  return @children[mth] if @children[mth]
21
23
 
24
+ res = @children[mth] = Sentofu::Resource.new(self, segment)
25
+
22
26
  if mth == :[]
23
- @children[:[]] = res = Sentofu::Resource.new(self, m[1])
24
- define_singleton_method(:[]) { |i| res.index = i; res }
25
- res
27
+ define_singleton_method(:[]) { |i|
28
+ Thread.current.thread_variable_set(TVP + segment, i); res }
26
29
  else
27
- @children[mth] = res = Sentofu::Resource.new(self, segment)
28
- define_singleton_method(mth) { res }
29
- res
30
+ define_singleton_method(mth) {
31
+ res }
30
32
  end
33
+
34
+ res
31
35
  end
32
36
 
33
37
  def add_leaf_segment(segment, point)
@@ -45,49 +49,105 @@ module Sentofu
45
49
 
46
50
  def fetch(segment, point, index, query)
47
51
 
48
- p [ :fetch, segment, '(point)', index, query ]
49
- p path(segment)
52
+ #p [ :fetch, segment, '(point)', index, query ]
53
+ #p path(segment)
50
54
  #pp point
51
- self.index = index if index
55
+ Thread.current.thread_variable_set(TVP + segment, index) if index
56
+
57
+ q = rectify_query_parameters(point, query)
58
+
59
+ pa = File.join(path, segment)
60
+ pa = pa.gsub(/_/, '-')
61
+
62
+ pa = pa + '?' + URI.encode_www_form(q) if q.any?
52
63
 
53
- validate_query_parameters(point, query)
64
+ return query.merge(path: pa) if query[:debug]
54
65
 
55
- nil
66
+ JSON.parse(Sentofu::Http.get(pa, api.token).body)
56
67
  end
57
68
 
58
- def path(segment=nil)
69
+ def path
59
70
 
60
- segment ||= self.segment
61
- segment = segment.gsub(/_/, '-')
71
+ seg =
72
+ segment[0, 1] == '{' ?
73
+ Thread.current.thread_variable_get(TVP + segment).to_s :
74
+ segment
62
75
 
63
76
  if parent
64
- File.join(parent.send(:path), segment)
77
+ File.join(parent.send(:path), seg)
65
78
  else
66
- '/' + segment
79
+ seg
67
80
  end
68
81
  end
69
82
 
70
- def validate_query_parameters(point, query)
83
+ def rectify_query_parameters(point, query)
84
+
85
+ q = query
86
+ .inject({}) { |h, (k, v)|
87
+ next h if k == :debug
88
+ h[k.to_s.gsub(/_/, '-')] =
89
+ case v
90
+ when Symbol then v.to_s
91
+ when Array then v.collect(&:to_s).join(',')
92
+ #when Time, Date then v.utc.strftime('%F')
93
+ when Time, Date then v.strftime('%F')
94
+ else v
95
+ end
96
+ h }
97
+
98
+ point['get']['parameters']
99
+ .each { |par|
100
+
101
+ next if par['in'] != 'query'
102
+
103
+ nam = par['name']
104
+ key = nam.gsub(/-/, '_')
105
+
106
+ fail ArgumentError.new(
107
+ "missing query parameter :#{key}"
108
+ ) if par['required'] == true && !q.has_key?(nam)
109
+
110
+ v = q[nam]
111
+ typ = par['schema']['type']
112
+
113
+ fail ArgumentError.new(
114
+ "argument to :#{key} not an integer"
115
+ ) if v && typ == 'integer' && ! v.is_a?(Integer)
116
+ fail ArgumentError.new(
117
+ "argument to :#{key} not a string (or a symbol)"
118
+ ) if v && typ == 'string' && ! v.is_a?(String)
119
+
120
+ enu = par['schema']['enum']
121
+
122
+ fail ArgumentError.new(
123
+ "value #{v.inspect} for :#{key} not present in #{enu.inspect}"
124
+ ) if v && enu && ! enu.include?(v) }
125
+
126
+ q
127
+ end
128
+
129
+ def api
71
130
 
72
- point['get']['parameters'].select { |pa| pa['in'] == 'query' }
73
- .each { |pa|
74
- k = (pa[:key] ||= pa['name'].gsub(/-/, '_').to_sym)
75
- fail ArgumentError.new("missing query parameter #{k.inspect}") \
76
- if pa['required'] == true && !query.has_key?(k) }
77
- pp params
131
+ parent ? parent.api : self
78
132
  end
79
133
  end
80
134
 
81
135
  class Api < Resource
82
136
 
83
137
  attr_reader :spec
138
+ attr_accessor :credentials
84
139
 
85
- def initialize(spec, name)
140
+ def initialize(name, spec)
86
141
 
87
- super(nil, nil)
142
+ super(nil, spec['servers'].first['url'])
88
143
 
89
- @spec = spec
90
144
  @name = name
145
+ @spec = spec
146
+
147
+ @credentials = nil
148
+ @token = nil
149
+
150
+ inflate_parameters
91
151
 
92
152
  #puts "================== #{name}"
93
153
  spec['paths'].each do |path, point|
@@ -103,17 +163,71 @@ pp params
103
163
  end
104
164
  end
105
165
 
106
- class << self
166
+ def token
107
167
 
108
- def make(api_spec)
168
+ if @token && @token.not_expired?
169
+ @token
170
+ else
171
+ @token = Sentofu::Http.fetch_token(@credentials)
172
+ end
173
+ end
174
+
175
+ def paths
176
+
177
+ @spec['paths']
178
+ end
179
+
180
+ def query(path, params={})
181
+
182
+ pa = server + path
183
+ pa = pa + '?' + URI.encode_www_form(params) if params.any?
184
+
185
+ return params.merge(path: pa) if params[:debug]
186
+
187
+ JSON.parse(Sentofu::Http.get(pa, token).body)
188
+ end
109
189
 
110
- name =
111
- api_spec['info']['title'].split(' - ').last[0..-4].strip
112
- .gsub(/([a-z])([A-Z])/) { |_| $1 + '_' + $2.downcase }
113
- .gsub(/([A-Z])/) { |c| c.downcase }
190
+ def modified
114
191
 
115
- api = Sentofu::Api.new(api_spec, name)
116
- (class << Sentofu; self; end).define_method(name) { api }
192
+ Time.parse(
193
+ @spec[:meta]['properties']
194
+ .find { |pr| pr['type'] == 'X-Modified' }['value'])
195
+ end
196
+
197
+ def version
198
+
199
+ @spec[:meta]['properties']
200
+ .find { |pr| pr['type'] == 'X-Version' }['value']
201
+ end
202
+
203
+ protected
204
+
205
+ def server
206
+
207
+ @spec['servers'].first['url']
208
+ end
209
+
210
+ def inflate_parameters
211
+
212
+ pars = @spec['components']['parameters']
213
+ return unless pars
214
+
215
+ refs = pars
216
+ .inject({}) { |h, (k, v)|
217
+ h["#/components/parameters/#{k}"] = v
218
+ h }
219
+
220
+ @spec['paths'].each do |_, pa|
221
+ pa.each do |_, me|
222
+ next unless me['parameters']
223
+ me['parameters'] = me['parameters']
224
+ .collect { |pm|
225
+ if ref = pm['$ref']
226
+ refs[ref] || fail("found no $ref #{ref.inspect} in spec")
227
+ else
228
+ pm
229
+ end }
230
+ end
117
231
  end
118
232
  end
119
233
  end
@@ -0,0 +1,47 @@
1
+
2
+ module Sentofu
3
+
4
+ class << self
5
+
6
+ def list_apis
7
+
8
+ detail_apis['apis']
9
+ .collect { |meta|
10
+
11
+ ps = meta['properties']
12
+
13
+ u = ps.find { |p| p['type'] == 'Swagger' }['url']
14
+ v = ps.find { |p| p['type'] == 'X-Version' }['value']
15
+
16
+ m = u.match(/intelligence_([^_]+)_api/)
17
+ n = m ? m[1] : meta['name']
18
+
19
+ d = meta['description']
20
+
21
+ { 'n' => n, 'v' => v, 'd' => d, 'u' => u } }
22
+ end
23
+
24
+ def detail_apis
25
+
26
+ Sentofu::Http.get_and_parse(
27
+ 'https://api.swaggerhub.com/apis/sentifi-api-docs/')
28
+ end
29
+
30
+ #- curl https://api.swaggerhub.com/apis/sentifi-api-docs/sentifi-intelligence_company_api/1.0.0/swagger.yaml > api_company.yaml
31
+ def dump_apis
32
+
33
+ puts
34
+
35
+ list_apis.each do |h|
36
+ n = h['n']; n = "auth" if n.match(/auth/i)
37
+ fn = "api_#{n}_#{h['v']}.yaml"
38
+ res = Sentofu::Http.get(h['u'] + '/swagger.yaml')
39
+ File.open(fn, 'wb') { |f| f.write(res.body) }
40
+ puts " wrote #{fn}"
41
+ end
42
+ end
43
+
44
+ #protected
45
+ end
46
+ end
47
+
data/lib/sentofu/http.rb CHANGED
@@ -3,39 +3,71 @@ module Sentofu
3
3
 
4
4
  module Http
5
5
 
6
- HOST = 'apis.sentifi.com'
7
-
8
6
  class << self
9
7
 
10
8
  def fetch_token(credentials=nil)
11
9
 
10
+ u = URI(Sentofu.auth_uri)
11
+
12
12
  cs = narrow_credentials(credentials)
13
13
 
14
14
  a = Base64.encode64("#{cs.id}:#{cs.secret}").strip
15
15
  #p a
16
16
 
17
- req = Net::HTTP::Post.new('/v1/oauth/token')
17
+ req = Net::HTTP::Post.new(u.path)
18
18
  req.add_field('Content-Type', 'application/json')
19
19
  req.add_field('Authorization', a)
20
20
 
21
21
  req.body = JSON.dump(
22
22
  grant_type: 'password', username: cs.user, password: cs.pass)
23
23
 
24
- res = request(req)
25
- #pp res
24
+ res = make_http(u).request(req)
25
+
26
+ Sentofu::Token.new(res)
27
+ end
28
+
29
+ def get(uri, token=nil)
30
+
31
+ u = URI(uri)
32
+
33
+ #t0 = Time.now
34
+ res = make_http(u).request(make_get_req(u, token))
35
+ #def res.headers; r = {}; each_header { |k, v| r[k] = v }; r; end
36
+ #puts "*** GET #{uri} took #{Time.now - t0}s"
26
37
 
27
- OpenStruct.new(JSON.parse(res.body))
38
+ res
39
+ end
40
+
41
+ def get_and_parse(uri, token=nil)
42
+
43
+ JSON.parse(get(uri, token).body)
28
44
  end
29
45
 
30
46
  protected
31
47
 
32
- def request(req)
48
+ def make_http(uri)
33
49
 
34
- t = Net::HTTP.new(HOST, 443)
35
- t.use_ssl = true
36
- #t.verify_mode = OpenSSL::SSL::VERIFY_NONE # avoid
50
+ #p uri.to_s
51
+ t = Net::HTTP.new(uri.host, uri.port)
52
+ t.use_ssl = (uri.scheme == 'https')
53
+ #t.set_debug_output($stdout) if uri.to_s.match(/search/)
37
54
 
38
- t.request(req)
55
+ t
56
+ end
57
+
58
+ def make_get_req(uri, token)
59
+
60
+ req = Net::HTTP::Get.new(uri.to_s)
61
+ req.instance_eval { @header.clear }
62
+ def req.set_header(k, v); @header[k] = [ v ]; end
63
+
64
+ req.set_header('User-Agent', Sentofu.user_agent)
65
+ req.set_header('Accept', 'application/json')
66
+
67
+ req.set_header('Authorization', token.header_value) if token
68
+ #pp req.instance_variable_get(:@header)
69
+
70
+ req
39
71
  end
40
72
 
41
73
  def narrow_credentials(o)
@@ -55,5 +87,24 @@ module Sentofu
55
87
  end
56
88
  end
57
89
  end
90
+
91
+ class Token
92
+
93
+ def initialize(res)
94
+
95
+ @h = JSON.parse(res.body)
96
+ @expires_at = Time.now + @h['expires_in']
97
+ end
98
+
99
+ def not_expired?
100
+
101
+ Time.now < @expires_at
102
+ end
103
+
104
+ def header_value
105
+
106
+ 'Bearer ' + @h['access_token']
107
+ end
108
+ end
58
109
  end
59
110
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sentofu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Mettraux
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-18 00:00:00.000000000 Z
11
+ date: 2019-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -37,6 +37,7 @@ files:
37
37
  - README.md
38
38
  - lib/sentofu.rb
39
39
  - lib/sentofu/api.rb
40
+ - lib/sentofu/explo.rb
40
41
  - lib/sentofu/http.rb
41
42
  - sentofu.gemspec
42
43
  homepage: http://github.com/jmettraux/sentofu