Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass context through parsing process to enable following all content API... #10

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/kosapi_client/api_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ def find_builder_class(builder_name)
end

end
end
end
2 changes: 1 addition & 1 deletion lib/kosapi_client/entity/author.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def initialize(name)
@name = name
end

def self.parse(contents)
def self.parse(contents, context = {})
new(contents[:atom_name])
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/kosapi_client/entity/boolean.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module KOSapiClient
module Entity
class Boolean

def self.parse(str)
def self.parse(str, context = {})
return true if str == 'true'
return false if str == 'false'
raise "Boolean parsing failed, invalid string: #{str}"
Expand Down
30 changes: 17 additions & 13 deletions lib/kosapi_client/entity/data_mappings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ def to_hash
result
end

def dump
self.to_hash.to_yaml
end

private
def convert_value(val)
if val.respond_to? :to_hash
Expand Down Expand Up @@ -44,26 +48,26 @@ def attr_mappings
# @param [Hash] content hash structure from API response corresponding to single domain object
# @return [BaseEntity] parsed domain object
def parse(content, context = {})
instance = new()
set_mapped_attributes(instance, content)
instance = new
set_mapped_attributes(instance, content, context)
instance
end

# Creates new domain object instance and sets values
# of mapped domain object attributes from source hash.
# Attributes are mapped by .map_data method.
def set_mapped_attributes(instance, source_hash)
def set_mapped_attributes(instance, source_hash, context)
if self.superclass.respond_to? :set_mapped_attributes
self.superclass.set_mapped_attributes(instance, source_hash)
self.superclass.set_mapped_attributes(instance, source_hash, context)
end
raise "Missing data mappings for entity #{self}" unless @data_mappings
@data_mappings.each do |name, options|
set_mapped_attribute(instance, name, source_hash, options)
set_mapped_attribute(instance, name, source_hash, options, context)
end
end

private
def set_mapped_attribute(instance, name, source_hash, mapping_options)
def set_mapped_attribute(instance, name, source_hash, mapping_options, context)
namespace = mapping_options[:namespace]
src_element = mapping_options[:element] || name
if namespace
Expand All @@ -80,29 +84,29 @@ def set_mapped_attribute(instance, name, source_hash, mapping_options)
return
end
else
value = convert_type(value, mapping_options[:type])
value = convert_type value, mapping_options[:type], context
end
instance.send("#{name}=".to_sym, value)
end

def convert_type(value, type)
def convert_type(value, type, context = {})
return value.to_i if type == Integer
return value if type == String
return convert_array(value, type.first) if type.is_a?(Array)
return convert_array(value, type.first, context) if type.is_a?(Array)

return type.parse(value) if type.respond_to? :parse
return type.parse(value, context) if type.respond_to? :parse
raise "Unknown type #{type} to convert value #{value} to."
end

# Converts values of array type to proper domain objects.
# It checks whether the value is really an array, because
# when API returns a single value it does not get parsed
# into an array.
def convert_array(values, type)
def convert_array(values, type, context)
if values.is_a?(Array)
values.map { |it| convert_type(it, type) }
values.map { |it| convert_type(it, type, context) }
else
[ convert_type(values, type) ]
[ convert_type(values, type, context) ]
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/kosapi_client/entity/enum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module KOSapiClient
module Entity
class Enum

def self.parse(contents)
def self.parse(contents, context = {})
contents.downcase.to_sym
end

Expand Down
2 changes: 1 addition & 1 deletion lib/kosapi_client/entity/id.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module KOSapiClient
module Entity
class Id < String

def self.parse(str)
def self.parse(str, context = {})
id = str.split(':').last
new(id)
end
Expand Down
12 changes: 5 additions & 7 deletions lib/kosapi_client/entity/link.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,29 @@ class Link

attr_reader :link_title, :link_href, :link_rel

def initialize(title, href, rel, client = nil)
def initialize(title, href, rel, client)
@link_title = title
@link_href = escape_url(href)
@link_rel = rel
@client = client
end

def self.parse(contents)
def self.parse(contents, context)
href = contents[:xlink_href] || contents[:href]
new(contents[:__content__], href, contents[:rel])
new(contents[:__content__], href, contents[:rel], context[:client])
end

def link_id
@link_href.split('/').last
end

def follow
return @target unless @target.nil?

raise "HTTP client not set, cannot send request to #{link_href}" unless @client
@client.send_request(:get, link_href)
end

def inject_client(client)
@client = client
end

def target
@target ||= follow
end
Expand Down
2 changes: 1 addition & 1 deletion lib/kosapi_client/entity/ml_string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def to_s(lang = :implicit)
@translations[lang]
end

def self.parse(item)
def self.parse(item, context = {})
unless item.is_a?(Array)
item = [item]
end
Expand Down
12 changes: 9 additions & 3 deletions lib/kosapi_client/http_client.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module KOSapiClient
class HTTPClient

def initialize(http_adapter, preprocessor = ResponsePreprocessor.new, converter = ResponseConverter.new(self))
def initialize(http_adapter, preprocessor = ResponsePreprocessor.new, converter = ResponseConverter.new)
@http_adapter = http_adapter
@preprocessor = preprocessor
@converter = converter
Expand All @@ -15,8 +15,8 @@ def send_request(verb, url, options = {})

def process_response(result)
preprocessed = @preprocessor.preprocess(result)
response = KOSapiClient::KOSapiResponse.new(preprocessed)
@converter.convert(response)
response = KOSapiClient::KOSapiResponse.new preprocessed
@converter.convert response, create_context
end

def get_absolute_url(url)
Expand All @@ -32,5 +32,11 @@ def is_absolute(url)
url.start_with?('http')
end

def create_context
{
client: self
}
end

end
end
2 changes: 1 addition & 1 deletion lib/kosapi_client/kosapi_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ def config

end

end
end
30 changes: 12 additions & 18 deletions lib/kosapi_client/response_converter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,11 @@ module KOSapiClient
# determined at runtime based on API response.

class ResponseConverter

def initialize(client)
@client = client
end

def convert(response)
def convert(response, context = {})
if response.is_paginated?
convert_paginated(response)
convert_paginated(response, context)
else
convert_single(response.item)
convert_single(response.item, context)
end
end

Expand All @@ -24,20 +19,20 @@ def convert(response)
# @param response [KOSapiResponse] Response object wrapping array of hashes corresponding to entries
# @return [ResultPage] ResultPage of domain objects

def convert_paginated(response)
def convert_paginated(response, context)
items = response.items || []
converted_items = items.map{ |p| convert_single(p) }
Entity::ResultPage.new(converted_items, create_links(response))
converted_items = items.map{ |p| convert_single(p, context) }
Entity::ResultPage.new(converted_items, create_links(response, context))
end

def convert_single(item)
def convert_single(item, context)
type = detect_type(item)
convert_type(item, type)
convert_type(item, type, context)
end

private
def convert_type(hash, type)
type.parse(hash)
def convert_type(hash, type, context)
type.parse(hash, context)
end

def detect_type(hash)
Expand All @@ -55,9 +50,8 @@ def extract_type(type_str)
entity_type
end

def create_links(response)
ResponseLinks.parse(response.links_hash, @client)
def create_links(response, context)
ResponseLinks.parse(response.links_hash, context)
end

end
end
13 changes: 6 additions & 7 deletions lib/kosapi_client/response_links.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,19 @@ def initialize(prev_link, next_link)

class << self

def parse(hash, client)
prev_link = parse_link(hash, 'prev', client)
next_link = parse_link(hash, 'next', client)
def parse(hash, context)
prev_link = parse_link(hash, 'prev', context)
next_link = parse_link(hash, 'next', context)
new(prev_link, next_link)
end

private
def parse_link(hash, rel, client)
def parse_link(hash, rel, context)
return nil unless hash
link_hash = extract_link_hash(hash, rel)

if link_hash
link = Entity::Link.parse(link_hash)
link.inject_client(client)
link
Entity::Link.parse(link_hash, context)
end
end

Expand Down
8 changes: 7 additions & 1 deletion spec/integration/parallels_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,14 @@
expect(parallel.link.link_rel).not_to be_nil
end

it 'follows reference link properly' do
teacher = client.parallels.find(339540000).teachers.first

expect(teacher.username).to eq("balikm")
end

it 'parses timetable slot ID' do
page = client.parallels
page = client.parallels.query('course.code' => 'MI-PAA')
slot = page.items.first.timetable_slots.first
expect(slot.id).not_to be_nil
end
Expand Down
10 changes: 2 additions & 8 deletions spec/kosapi_client/entity/link_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
Link = KOSapiClient::Entity::Link

let(:client) { instance_double(KOSapiClient::HTTPClient) }
subject(:link) { Link.parse({href: 'http://example.com/foo/bar/42', __content__: 'Example Site', rel: 'next'}) }
subject(:link) { Link.parse({href: 'http://example.com/foo/bar/42', __content__: 'Example Site', rel: 'next'}, {client: client}) }
let(:result) { double(:result, foo: :bar) }
before(:example) { link.inject_client(client) }

describe '.parse' do

Expand All @@ -23,7 +22,7 @@

it 'encodes href URL' do
href = 'parallels?query=(lastUpdatedDate%3E=2014-07-01T00:00:00;lastUpdatedDate%3C=2014-07-10T00:00:00)&offset=10&limit=10'
link = Link.new(nil, href, nil)
link = Link.new(nil, href, nil, nil)
expect(link.link_href).to eq 'parallels?query=(lastUpdatedDate%3E=2014-07-01T00:00:00%3BlastUpdatedDate%3C=2014-07-10T00:00:00)&offset=10&limit=10'
end

Expand All @@ -39,11 +38,6 @@

describe '#follow' do

it 'throws error when not http client set' do
link.inject_client(nil)
expect { link.follow }.to raise_error(RuntimeError)
end

it 'calls http client with href' do
expect(client).to receive(:send_request).with(:get, 'http://example.com/foo/bar/42')
link.follow
Expand Down
14 changes: 10 additions & 4 deletions spec/kosapi_client/entity/parallel_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@
teacher: [{ xlink_href: 'teachers/smitkdan/', __content__: 'Ing. arch. Daniel Smitka Ph.D.' }]
} }

let(:client) { instance_double(KOSapiClient::HTTPClient) }

it 'parses parallel attributes' do
parallel = KOSapiClient::Entity::Parallel.parse(attributes)
parallel = KOSapiClient::Entity::Parallel.parse(attributes, {client: client})
expect(parallel.code).to eq 42
expect(parallel.capacity_overfill).to eq :denied
expect(parallel.teachers.first).to be_an_instance_of KOSapiClient::Entity::Link
expect(parallel.teachers.first.link_href).to eq 'teachers/smitkdan/'
expect(parallel.teachers.first.link_title).to eq 'Ing. arch. Daniel Smitka Ph.D.'
expect(parallel.teachers).to be_a(Array)
expect(parallel.teachers.first).to be_instance_of KOSapiClient::Entity::Link

link_data = parallel.teachers.first.to_hash

expect(link_data[:href]).to eq 'teachers/smitkdan/'
expect(link_data[:title]).to eq 'Ing. arch. Daniel Smitka Ph.D.'

end
end
12 changes: 9 additions & 3 deletions spec/kosapi_client/entity/result_page_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,22 @@

ResultPage = KOSapiClient::Entity::ResultPage

let(:links) { KOSapiClient::ResponseLinks.new(nil, next_link) }
subject(:result_page) { ResultPage.new([item], links) }
let(:item) { double(:item) }
let(:item2) { double(:second_item) }
let(:links) { instance_double(KOSapiClient::ResponseLinks, next: next_link) }
let(:next_page) { ResultPage.new([item2], instance_double(KOSapiClient::ResponseLinks, next: nil)) }
let(:next_link) { instance_double(KOSapiClient::Entity::Link, follow: next_page) }
let(:next_page) { ResultPage.new([item2], KOSapiClient::ResponseLinks.new(nil, nil)) }
let(:next_link) { link = KOSapiClient::Entity::Link.new(nil, "/", nil, nil) }

before(:each) do
next_link.instance_variable_set(:@target, next_page)
end


describe '#each' do

it 'is auto-paginated by default' do

[item, item2].each { |it| expect(it).to receive(:foo) }
result_page.each { |it| it.foo }
end
Expand Down
Loading