-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Antti Pitkanen
committed
Sep 2, 2014
1 parent
3d13b9a
commit 584999a
Showing
26 changed files
with
2,017 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,8 @@ | |
/test/version_tmp/ | ||
/tmp/ | ||
|
||
.DS_Store | ||
|
||
## Specific to RubyMotion: | ||
.dat* | ||
.repl_history | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
--color | ||
--warnings | ||
--require spec_helper |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
gem 'multi_json' | ||
gem 'oj' | ||
gem 'faraday' | ||
|
||
group :test do | ||
gem 'rspec' | ||
gem 'webmock' | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
GEM | ||
specs: | ||
addressable (2.3.6) | ||
crack (0.4.2) | ||
safe_yaml (~> 1.0.0) | ||
diff-lcs (1.2.5) | ||
faraday (0.9.0) | ||
multipart-post (>= 1.2, < 3) | ||
multi_json (1.10.1) | ||
multipart-post (2.0.0) | ||
oj (2.9.8) | ||
rspec (3.0.0) | ||
rspec-core (~> 3.0.0) | ||
rspec-expectations (~> 3.0.0) | ||
rspec-mocks (~> 3.0.0) | ||
rspec-core (3.0.4) | ||
rspec-support (~> 3.0.0) | ||
rspec-expectations (3.0.4) | ||
diff-lcs (>= 1.2.0, < 2.0) | ||
rspec-support (~> 3.0.0) | ||
rspec-mocks (3.0.4) | ||
rspec-support (~> 3.0.0) | ||
rspec-support (3.0.4) | ||
safe_yaml (1.0.3) | ||
webmock (1.18.0) | ||
addressable (>= 2.3.6) | ||
crack (>= 0.3.2) | ||
|
||
PLATFORMS | ||
ruby | ||
|
||
DEPENDENCIES | ||
faraday | ||
multi_json | ||
oj | ||
rspec | ||
webmock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
module Wsapi | ||
class Object | ||
attr_reader :raw_data | ||
|
||
def initialize(raw_data) | ||
@raw_data = raw_data | ||
end | ||
|
||
def name | ||
@raw_data['_refObjectName'] | ||
end | ||
|
||
def id | ||
@raw_data['ObjectID'] | ||
end | ||
|
||
def url | ||
@raw_data['_ref'] | ||
end | ||
|
||
def workspace | ||
@raw_data["Workspace"]["_refObjectName"] | ||
end | ||
|
||
def self.from_data(type, raw_data) | ||
if type && Wsapi.const_defined?(type) | ||
Wsapi.const_get(type).new(raw_data) | ||
else | ||
Object.new(raw_data) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module Wsapi | ||
class Project < Wsapi::Object | ||
def subscription | ||
@subscription ||= Wsapi::Subscription.new(@raw_data["Subscription"]) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module Wsapi | ||
class Subscription < Wsapi::Object | ||
def subscription_id | ||
@raw_data["SubscriptionID"].to_s | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
module Wsapi | ||
class User < Wsapi::Object | ||
def username | ||
@raw_data["UserName"] | ||
end | ||
|
||
def first_name | ||
@raw_data["FirstName"] | ||
end | ||
|
||
def last_name | ||
@raw_data["LastName"] | ||
end | ||
|
||
def name | ||
"#{@raw_data['FirstName']} #{@raw_data['LastName']}" | ||
end | ||
|
||
def email | ||
@raw_data["EmailAddress"] | ||
end | ||
|
||
def admin? | ||
@raw_data["SubscriptionAdmin"] | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
require 'oj' | ||
require 'multi_json' | ||
require 'faraday' | ||
|
||
require 'wsapi/models/object' | ||
require 'wsapi/models/subscription' | ||
require 'wsapi/models/user' | ||
require 'wsapi/models/project' | ||
|
||
module Wsapi | ||
class StandardErrorWithResponse < StandardError | ||
attr_reader :response | ||
def initialize(msg, response = nil) | ||
@response = response | ||
super(msg) | ||
end | ||
end | ||
class AuthorizationError < StandardErrorWithResponse; end | ||
class ApiError < StandardErrorWithResponse; end | ||
class ObjectNotFoundError < StandardErrorWithResponse; end | ||
class IpAddressLimited < StandardErrorWithResponse; end | ||
|
||
WSAPI_URL = ENV['WSAPI_URL'] || '' | ||
|
||
class ZuulAuthentication < Faraday::Middleware | ||
def initialize(logger, zuul_session_id) | ||
@zuul_session_id = zuul_session_id | ||
super(logger) | ||
end | ||
|
||
def call(env) | ||
env[:request_headers]['ZSESSIONID'] = @zuul_session_id | ||
@app.call(env) | ||
end | ||
end | ||
|
||
class Mapper | ||
def self.get_errors(json) | ||
if result = json["QueryResult"] | ||
result["Errors"] | ||
elsif result = json["OperationResult"] | ||
result["Errors"] | ||
else | ||
[] | ||
end | ||
end | ||
|
||
def self.get_object(response) | ||
json = MultiJson.load(response.body) | ||
if get_errors(json).empty? && json.size == 1 | ||
Wsapi::Object.from_data(json.keys.first, json.values.first) | ||
else | ||
raise ApiError.new("Errors: #{get_errors(json).inspect}", response) | ||
end | ||
rescue MultiJson::LoadError, Oj::ParseError | ||
raise ApiError.new("Invalid JSON response from WSAPI: #{response.body}", response) | ||
end | ||
|
||
def self.get_objects(response) | ||
json = MultiJson.load(response.body) | ||
if get_errors(json).empty? && query_result = json["QueryResult"] | ||
query_result["Results"].map { |object| Wsapi::Object.from_data(object["_type"], object) } | ||
else | ||
raise ApiError.new("Errors: #{get_errors(json).inspect}", response) | ||
end | ||
rescue MultiJson::LoadError, Oj::ParseError | ||
raise ApiError.new("Invalid JSON response from WSAPI: #{response.body}", response) | ||
end | ||
end | ||
|
||
class Session | ||
def initialize(session_id, opts = {}) | ||
@api_version = opts[:version] || "3.0" | ||
@session_id = session_id | ||
@workspace_id = opts[:workspace_id] | ||
@conn = Faraday.new(ssl: { verify: false} ) do |faraday| | ||
faraday.request :url_encoded # form-encode POST params | ||
faraday.use ZuulAuthentication, @session_id | ||
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP | ||
end | ||
end | ||
|
||
def get_user_subscription | ||
response = wsapi_request(wsapi_resource_url("Subscription")) | ||
Mapper.get_object(response) | ||
end | ||
|
||
def get_subscription(id) | ||
response = wsapi_request(wsapi_resource_url("Subscription/#{id}")) | ||
Mapper.get_object(response) | ||
end | ||
|
||
def get_projects(opts = {}) | ||
fetch_with_pages(opts) do |page_query| | ||
wsapi_request(wsapi_resource_url("Project"), opts.merge(page_query)) | ||
end | ||
end | ||
|
||
def get_project(id) | ||
response = wsapi_request(wsapi_resource_url("Project/#{id}")) | ||
Mapper.get_object(response) | ||
end | ||
|
||
def get_current_user | ||
response = wsapi_request(wsapi_resource_url("User")) | ||
Mapper.get_object(response) | ||
end | ||
|
||
def get_user(id) | ||
response = wsapi_request(wsapi_resource_url("User/#{id}")) | ||
Mapper.get_object(response) | ||
end | ||
|
||
def get_user_by_username(username) | ||
response = wsapi_request(wsapi_resource_url("User"), query: "(UserName = \"#{username}\")", pagesize: 1) | ||
(Mapper.get_objects(response) ||[]).first | ||
end | ||
|
||
def get_team_members(project_id, opts = {}) | ||
fetch_with_pages(opts) do |page_query| | ||
wsapi_request(wsapi_resource_url("Project/#{project_id}/TeamMembers"), opts.merge(page_query)) | ||
end | ||
end | ||
|
||
def get_editors(project_id, opts = {}) | ||
fetch_with_pages(opts) do |page_query| | ||
wsapi_request(wsapi_resource_url("Project/#{project_id}/Editors"), opts.merge(page_query)) | ||
end | ||
end | ||
|
||
private | ||
|
||
def workspace_url | ||
wsapi_resource_url("Workspace/#{@workspace_id}") | ||
end | ||
|
||
def wsapi_resource_url(resource) | ||
File.join(WSAPI_URL, "v#{@api_version}", resource) | ||
end | ||
|
||
def wsapi_request(url, opts = {}) | ||
response = @conn.get do |req| | ||
req.url url | ||
req.params['workspace'] = workspace_url if @workspace_id | ||
req.params['query'] = opts[:query] if opts[:query] | ||
req.params['start'] = opts[:start] || 1 | ||
req.params['pagesize'] = opts[:pagesize] || 200 | ||
req.params['fetch'] = opts[:fetch] || true # by default, fetch full objects | ||
end | ||
raise AuthorizationError.new("Unauthorized", response) if response.status == 401 | ||
raise ApiError.new("Internal server error", response) if response.status == 500 | ||
raise ObjectNotFoundError.new("Object not found") if object_not_found?(response) | ||
raise IpAddressLimited.new("IP Address limited", response) if ip_address_limited?(response) | ||
response | ||
end | ||
|
||
def ip_address_limited?(response) | ||
limit_message = /Your IP address, (?:\d+\.?)+, is not within the allowed range that your subscription administrator has configured./ | ||
response.status > 401 && response.body.match(limit_message) | ||
end | ||
|
||
def object_not_found?(response) | ||
if response.status == 200 | ||
result = MultiJson.load(response.body)["OperationResult"] | ||
if result && error = result["Errors"].first | ||
error.match("Cannot find object to read") | ||
else | ||
false | ||
end | ||
else | ||
false | ||
end | ||
end | ||
|
||
def fetch_with_pages(opts = {}, &block) | ||
page_query = { | ||
start: opts[:start] || 1, | ||
pagesize: opts[:pagesize] || 100 | ||
} | ||
resultCount = nil | ||
objects = [] | ||
while(!resultCount || resultCount > objects.size) do | ||
response = yield(page_query) | ||
resultCount = MultiJson.load(response.body)["QueryResult"]["TotalResultCount"] | ||
objects += Mapper.get_objects(response) | ||
page_query[:start] += page_query[:pagesize] | ||
end | ||
objects | ||
end | ||
end | ||
end | ||
|
Oops, something went wrong.