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

Support for running in AWS Lambda with no native extensions #17

Open
wants to merge 8 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
6 changes: 3 additions & 3 deletions alexa_ruby.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ Gem::Specification.new do |spec|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ['lib']

spec.add_runtime_dependency 'bundler', '>= 1.6.9'
spec.add_runtime_dependency 'rake'
spec.add_runtime_dependency 'oj', '~> 3.0'

spec.add_runtime_dependency 'addressable', '>= 2.5.1'
spec.add_runtime_dependency 'httparty', '>= 0.15.5'

spec.add_development_dependency 'bundler', '>= 1.6.9'
spec.add_development_dependency 'rake'
spec.add_development_dependency 'minitest', '~> 5.10', '>= 5.10.2'
spec.add_development_dependency 'minitest-reporters', '~> 1.1', '>= 1.1.14'
end
8 changes: 5 additions & 3 deletions lib/alexa_ruby.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Utilities
require 'addressable/uri'
require 'oj'
require 'securerandom'
require 'json'
require 'ostruct'

# Gem core
require 'alexa_ruby/alexa'
Expand All @@ -18,6 +19,7 @@
require 'alexa_ruby/request/launch_request'
require 'alexa_ruby/request/intent_request'
require 'alexa_ruby/request/intent_request/slot'
require 'alexa_ruby/request/intent_request/resolution_authority'
require 'alexa_ruby/request/session_ended_request'
require 'alexa_ruby/response/response'
require 'alexa_ruby/response/audio_player'
Expand Down Expand Up @@ -51,8 +53,8 @@ def new(request, opts = {})
# @return [Hash] valid builded JSON
# @raise [ArgumentError] if given object isn't a valid JSON object
def build_json(obj)
obj = Oj.generate(obj) if hash?(obj)
Oj.load(obj, symbol_keys: true)
obj = JSON.generate(obj) if hash?(obj)
JSON.parse(obj, symbolize_names: true)
rescue StandardError
raise ArgumentError, 'Request must be a valid JSON object'
end
Expand Down
2 changes: 1 addition & 1 deletion lib/alexa_ruby/request/base_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def valid?
#
# @return [String] request json
def json
Oj.to_json(@req)
JSON.generate(@req)
end

private
Expand Down
2 changes: 1 addition & 1 deletion lib/alexa_ruby/request/base_request/validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def valid_uri?
#
# @return [Boolean]
def valid_certificates?
Certificates.new(@chain_url, @signature, Oj.to_json(@request)).valid?
Certificates.new(@chain_url, @signature, JSON.generate(@request)).valid?
end
end
end
34 changes: 34 additions & 0 deletions lib/alexa_ruby/request/intent_request/resolution_authority.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module AlexaRuby
# Class that encapsulates each slot
class ResolutionAuthority
attr_accessor :authority, :status_code, :values

# Initialize slot and define its name and value
#
# @param resolution_authority [Hash] resolution_authority parameters
def initialize(resolution_authority)
@resolution_authority = resolution_authority
@authority = @resolution_authority[:authority]
@status_code = define_status_code
@values = @resolution_authority[:values]&.map { |value| OpenStruct.new(value[:value]) } || []
end

private

# Define user confirmation status
#
# @return [Symbol] current authority status code
def define_status_code
case @resolution_authority.dig(:status, :code)
when 'ER_SUCCESS_MATCH'
:success_match
when 'ER_SUCCESS_NO_MATCH'
:success_no_match
when 'ER_ERROR_TIMEOUT'
:error_timeout
when 'ER_ERROR_EXCEPTION'
:error_exception
end
end
end
end
20 changes: 19 additions & 1 deletion lib/alexa_ruby/request/intent_request/slot.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module AlexaRuby
# Class that encapsulates each slot
class Slot
attr_accessor :name, :value, :confirmation_status
attr_accessor :name, :value, :confirmation_status, :resolution_authorities, :resolved_values

# Initialize slot and define its name and value
#
Expand All @@ -12,6 +12,8 @@ def initialize(slot)
@name = @slot[:name]
@value = @slot[:value]
@confirmation_status = define_confirmation_status
@resolution_authorities = define_resolution_authorities
@resolved_values = define_resolved_values
end

private
Expand All @@ -36,5 +38,21 @@ def define_confirmation_status
:denied
end
end

# Define entity resolution authorities
#
# @return [Array] resolution authorities
def define_resolution_authorities
authorities = @slot.dig(:resolutions, :resolutionsPerAuthority)
&.map { |resolution_authority| ResolutionAuthority.new(resolution_authority) }
authorities || []
end

# Define entity resolution values
#
# @return [Array] resolved values
def define_resolved_values
@resolution_authorities.map(&:values)&.flatten
end
end
end
2 changes: 1 addition & 1 deletion lib/alexa_ruby/response/response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def add_audio_player_directive(directive, params = {})
#
# @return [JSON] response object
def json
Oj.to_json(@resp)
JSON.generate(@resp)
end

# Tell something to Alexa user and close conversation.
Expand Down
2 changes: 1 addition & 1 deletion spec/fixtures/request/intent_request.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
},
"request": {
"type": "IntentRequest",
"requestId": " amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
"requestId": "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
"timestamp": "2015-05-13T12:34:56Z",
"dialogState": "COMPLETED",
"locale": "string",
Expand Down
77 changes: 77 additions & 0 deletions spec/fixtures/request/resolution_request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"version": "1.0",
"session": {
"new": false,
"sessionId": "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000",
"application": {
"applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
},
"user": {
"userId": "amzn1.account.AM3B00000000000000000000000"
}
},
"context": {
"System": {
"application": {
"applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
},
"user": {
"userId": "amzn1.account.AM3B00000000000000000000000"
},
"device": {
"deviceId": "TEST",
"supportedInterfaces": {}
},
"apiEndpoint": "https://api.amazonalexa.com",
"apiAccessToken": "token"
},
"Viewport": {
"experiences": [
{
"arcMinuteWidth": 246,
"arcMinuteHeight": 144,
"canRotate": false,
"canResize": false
}
],
"shape": "RECTANGLE",
"pixelWidth": 1024,
"pixelHeight": 600,
"dpi": 160,
"currentPixelWidth": 1024,
"currentPixelHeight": 600,
"touch": [
"SINGLE"
]
}
},
"request": {
"type": "IntentRequest",
"requestId": "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
"timestamp": "2019-02-07T14:49:57Z",
"locale": "en-US",
"dialogState": "COMPLETED",
"intent": {
"name": "RebuildLatest",
"confirmationStatus": "CONFIRMED",
"slots": {
"statusFilter": {
"name": "statusFilter",
"value": "yeah",
"confirmationStatus": "NONE",
"source": "USER",
"resolutions": {
"resolutionsPerAuthority": [
{
"authority": "skill.arn.statusFilter",
"status": {
"code": "ER_SUCCESS_NO_MATCH"
}
}
]
}
}
}
}
}
}
91 changes: 91 additions & 0 deletions spec/fixtures/request/resolution_success_request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"version": "1.0",
"session": {
"new": false,
"sessionId": "amzn1.echo-api.session.0000000-0000-0000-0000-00000000000",
"application": {
"applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
},
"user": {
"userId": "amzn1.account.AM3B00000000000000000000000"
}
},
"context": {
"System": {
"application": {
"applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe"
},
"user": {
"userId": "amzn1.account.AM3B00000000000000000000000"
},
"device": {
"deviceId": "TEST",
"supportedInterfaces": {}
},
"apiEndpoint": "https://api.amazonalexa.com",
"apiAccessToken": "token"
},
"Viewport": {
"experiences": [
{
"arcMinuteWidth": 246,
"arcMinuteHeight": 144,
"canRotate": false,
"canResize": false
}
],
"shape": "RECTANGLE",
"pixelWidth": 1024,
"pixelHeight": 600,
"dpi": 160,
"currentPixelWidth": 1024,
"currentPixelHeight": 600,
"touch": [
"SINGLE"
]
}
},
"request": {
"type": "IntentRequest",
"requestId": "amzn1.echo-api.request.0000000-0000-0000-0000-00000000000",
"timestamp": "2019-02-06T22:09:26Z",
"locale": "en-US",
"dialogState": "COMPLETED",
"intent": {
"name": "RebuildLatest",
"confirmationStatus": "CONFIRMED",
"slots": {
"edgeVersion": {
"name": "edgeVersion",
"value": "9732",
"confirmationStatus": "CONFIRMED",
"source": "USER"
},
"statusFilter": {
"name": "statusFilter",
"value": "all",
"confirmationStatus": "NONE",
"source": "USER",
"resolutions": {
"resolutionsPerAuthority": [
{
"authority": "skill.arn.statusFilter",
"status": {
"code": "ER_SUCCESS_MATCH"
},
"values": [
{
"value": {
"name": "both",
"id": "BOTH"
}
}
]
}
]
}
}
}
}
}
}
16 changes: 8 additions & 8 deletions spec/request/base_request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
end

it 'should build a Request object for valid Hash' do
alexa = AlexaRuby.new(Oj.load(@json))
alexa = AlexaRuby.new(JSON.parse(@json))
alexa.request.wont_be_nil
end

it 'should build a Response object for valid Hash' do
alexa = AlexaRuby.new(Oj.load(@json))
alexa = AlexaRuby.new(JSON.parse(@json))
alexa.response.wont_be_nil
end

Expand All @@ -52,28 +52,28 @@
end

it 'should raise ArgumentError if request ID is missing' do
req = Oj.load(@json, symbol_keys: true)
req = JSON.parse(@json, symbolize_names: true)
req[:request][:requestId] = nil
err = proc { AlexaRuby.new(req) }.must_raise ArgumentError
err.message.must_equal 'Missing request ID'
end

it 'should raise ArgumentError if request timestamp is missing' do
req = Oj.load(@json, symbol_keys: true)
req = JSON.parse(@json, symbolize_names: true)
req[:request][:timestamp] = nil
err = proc { AlexaRuby.new(req) }.must_raise ArgumentError
err.message.must_equal 'Missing request timestamp'
end

it 'should raise ArgumentError if request type unknown' do
req = Oj.load(@json, symbol_keys: true)
req = JSON.parse(@json, symbolize_names: true)
req[:request][:type] = 'dummy'
err = proc { AlexaRuby.new(req) }.must_raise ArgumentError
err.message.must_equal 'Unknown type of Alexa request'
end

it 'should raise ArgumentError if request structure isn\'t valid' do
req = Oj.load(@json, symbol_keys: true)
req = JSON.parse(@json, symbolize_names: true)
req[:request] = nil
msg = 'Invalid request structure, ' \
'please, refer to the Amazon Alexa manual: ' \
Expand All @@ -84,15 +84,15 @@
end

it 'should not raise ArgumentError if request structure isn\'t valid and validations are disabled' do
req = Oj.load(@json, symbol_keys: true)
req = JSON.parse(@json, symbolize_names: true)
req[:version] = nil
alexa = AlexaRuby.new(req, disable_validations: true)
alexa.request.version.must_be_nil
end

it 'should return received request in JSON format' do
alexa = AlexaRuby.new(@json)
sample = Oj.to_json(Oj.load(@json))
sample = JSON.generate(JSON.parse(@json))
alexa.request.json.must_equal sample
end
end
Expand Down
Loading