Skip to content
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
1 change: 1 addition & 0 deletions .rbnextrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
nextify: |
./lib
--min-version=2.6
--edge
--proposed
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ eval_gemfile "gemfiles/rubocop.gemfile"
eval_gemfile "gemfiles/rbs.gemfile"

gem "ruby-next", "~> 1.0.0"
gem "activefunction-core", path: "gems/activefunction-core"

local_gemfile = "#{File.dirname(__FILE__)}/Gemfile.local"

Expand Down
19 changes: 17 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,23 @@ GEMS_DIRS.each do |gem_dir|
Rake::TestTask.new("test_gem:#{gem_name(gem_dir)}") do |t|
t.libs << "#{gem_dir}/test"
t.libs << "#{gem_dir}/lib"
t.test_files = FileList["#{gem_dir}/test/**/*_test.rb"]
t.warning = false
t.verbose = true
t.test_files = FileList["#{gem_dir}/test/**/*_test.rb"].then do |test_files|
if RUBY_VERSION >= "3.2"
test_files
else
test_files.exclude("#{gem_dir}/test/functions/aws_lambda/**/*.rb")
end

test_files.exclude("#{gem_dir}/test/integration/**/*.rb")
end
end

task "test_gem:#{gem_name(gem_dir)}:integration" do
Dir.glob("#{gem_dir}/test/integration/**/*_test.rb").each do |test_file|
sh(Gem.ruby, "-I#{__dir__}/lib:#{__dir__}/test", test_file)
end || raise("Integration tests failed")
end

RuboCop::RakeTask.new("rubocop:#{gem_name(gem_dir)}") do |t|
Expand All @@ -38,6 +52,7 @@ desc "Run All Tests in each gem"
task "test:all" do
with_all_gems do |name|
Rake::Task["test_gem:#{name}"].invoke
Rake::Task["test_gem:#{name}:integration"].invoke
end
end

Expand All @@ -61,7 +76,7 @@ end
desc "Transpile all gems"
task "nextify:all" do
with_all_gems(false) do |path|
sh "bundle exec ruby-next nextify #{path}/lib -V"
sh "bundle exec ruby-next nextify #{path} -V"
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# frozen_string_literal: true

require "active_function_core/plugins/types/type"
require "active_function_core/plugins/types/validation"

module ActiveFunctionCore
module Plugins
module Types
require "active_function_core/plugins/types/type"
require "active_function_core/plugins/types/validation"

RawType = Class.new

module ClassMethods
Expand Down Expand Up @@ -34,11 +34,12 @@ def type(hash_attr)

raise ArgumentError, "type Class must be a RawType" unless klass < RawType

name = klass.name.split("::").last
remove_const(name.to_sym)
type_name = klass.name.split("::").last.to_sym
type_attributes = attributes.is_a?(Hash) ? Type.define(**attributes) : attributes

@__types ||= Set.new
@__types << const_set(name, Type.define(type_validator: Validation::TypeValidator, **attributes))
remove_const(type_name).tap do
(@__types ||= Set.new) << const_set(type_name, type_attributes)
end
end

def set_root_type(type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ module ActiveFunctionCore::Plugins::Types
CustomType = Struct.new(:wrapped_type)
Boolean = Class.new(CustomType)
Nullable = Class.new(CustomType)
Enum = Class.new(CustomType) do
def initialize(*types) = super(types)
end

class Type < Data
def self.define(type_validator:, **attributes, &block)
def self.define(type_validator: Validation::TypeValidator, **attributes, &block)
nillable_attributes = handle_nillable_attributes!(attributes)

super(*attributes.keys, &block).tap do |klass|
Expand Down Expand Up @@ -57,25 +60,34 @@ def _subtype_class(type)
end

def _build_attributes!(attributes)
attributes.map(&method(:_prepare_attribute)).to_h
attributes.map do |name, value|
if (type = schema[name])
type = _subtype_class(type) if type.is_a?(Class) && type < RawType
else
raise ArgumentError, "unknown attribute #{name}"
end

_prepare_attribute(name, value, type)
end.to_h
end

def _prepare_attribute(name, value)
raise ArgumentError, "unknown attribute #{name}" unless (type = schema[name])
raise(TypeError, "expected #{value} to be a #{type}") unless type_validator[value, type].valid?
def _prepare_attribute(name, value, type)
type_validator[value, type].validate!

serialized_value = _process_subtype_values(type, value) { |subtype, attrs| subtype[**attrs] }

[name, serialized_value]
end

def _process_subtype_values(type, value, &block)
if _subtype?(type)
yield _subtype_class(type), value
elsif type.is_a?(Array) && _subtype?(type.first)
value.map { |it| yield(_subtype_class(type.first), it) }
elsif type.is_a?(Hash) && _subtype?(type.values.first)
value.transform_values { |it| yield(_subtype_class(type.values.first), it) }
def _process_subtype_values(type, value, &transform_values_block)
type = type.wrapped_type if type.respond_to?(:wrapped_type)

if value && _subtype?(type)
transform_values_block[_subtype_class(type), value]
elsif value.is_a?(Array) && type.is_a?(Array) && _subtype?(type.first)
value.map { |it| transform_values_block[_subtype_class(type.first), it] }
elsif value.is_a?(Hash) && type.is_a?(Hash) && _subtype?(type.values.first)
value.transform_values { |it| transform_values_block[_subtype_class(type.values.first), it] }
else
value
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ def hash_type_validation(value, type_hash)
key_validator, value_validator = [type_validator_for(key_type), type_validator_for(value_type)]
literal_type_validation(value, Hash) && value.all? { |k, v| key_validator[k, key_type] && value_validator[v, value_type] }
end

def enum_type_validation(value, type_enum)
type_enum.any? { |it| literal_type_validation(value, it) }
end
end

def self.included(base)
Expand All @@ -59,7 +63,8 @@ def self.included(base)
Boolean => method(:boolean_type_validation),
Array => method(:array_type_validation),
Hash => method(:hash_type_validation),
Nullable => method(:nullable_type_validation)
Nullable => method(:nullable_type_validation),
Enum => method(:enum_type_validation)
}

private_constant :PRIMITIVE_TYPE_VALIDATORS_MAPPING
Expand All @@ -68,6 +73,12 @@ def self.included(base)
TypeValidator = Data.define(:value, :type) do
include ValidationMethods

def validate!
unless valid?
raise TypeError, "expected #{value} to be kind of #{type}"
end
end

def valid?
if type.is_a?(CustomType) && type.wrapped_type
validator_proc[value, type.wrapped_type]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ def test_boolean_validation

assert @subject.valid?
end

def test_enum_validation
@attrs = {value: "Hello", type: ::ActiveFunctionCore::Plugins::Types::Enum[String, Integer]}

assert @subject.valid?
end
end

class ValidationTest < Minitest::Test
Expand Down
9 changes: 8 additions & 1 deletion gems/activefunction-core/test/plugins/types_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,26 @@ class TypesIncludedTestClass
describe "::type" do
class TypeTestClass < TypesIncludedTestClass
type NamedType => {
string_attribute: String
string_attribute: String,
single_value_attribute: SingleValueType
}

type SingleValueType => ActiveFunctionCore::Plugins::Types::Enum[Symbol, String]
end

let(:klass) { TypeTestClass }

it "defines Constant < Type" do
_(subject.const_defined?(:NamedType)).must_equal true
_(subject.const_get(:NamedType)).must_be :<, described_class::Type

_(subject.const_defined?(:SingleValueType)).must_equal true
_(subject.const_get(:SingleValueType)).must_equal ActiveFunctionCore::Plugins::Types::Enum[Symbol, String]
end

it "adds type to types" do
_(subject.instance_variable_get(:@__types)).must_be :===, subject::NamedType
_(subject.instance_variable_get(:@__types)).must_be :===, subject::SingleValueType
end

it "redefines RawType to Type" do
Expand Down
2 changes: 1 addition & 1 deletion gems/activefunction-core/test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
require "minitest/autorun"
require "minitest/reporters"

require "active_function"
require "active_function_core"

Minitest::Reporters.use! [Minitest::Reporters::SpecReporter.new(color: true)]
19 changes: 19 additions & 0 deletions lib/active_function/functions/aws_lambda.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

module ActiveFunction
module Functions
module AwsLambda
if RUBY_VERSION < "3.2"
raise LoadError, <<~ERROR
Error: Unsupported Ruby version detected. AWS Lambda currently supports only Ruby 3.2.
For seamless integration, ensure your Ruby runtime version is up-to-date.
Explore AWS Lambda documentation for more details:

https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html
ERROR
end

ActiveFunction.register_plugin :aws_lambda, self
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true

require "active_function_core/plugins/types"

module ActiveFunction::Functions::AwsLambda
class ApiGatewayV2HttpApiProxyEvent
include ActiveFunctionCore::Plugins::Types

define_schema do
type Event => {
version: String,
routeKey: String,
rawPath: String,
rawQueryString: String,
cookies: Array[String],
headers: Hash[Symbol, String],
requestContext: RequestContext,
isBase64Encoded: Boolean,
"?body": String,
"?pathParameters": Hash[Symbol, String],
"?queryStringParameters": Hash[Symbol, String],
"?stageVariables": Hash[Symbol, String]
}

type RequestContext => {
accountId: String,
apiId: String,
"?authorizer": Authorizer,
"?authentication": Authentication,
domainName: String,
"?domainPrefix": String,
http: Http,
requestId: String,
routeKey: String,
stage: String,
time: String,
timeEpoch: Integer
}

type Authentication => {
clientCert: ClientCert
}

type ClientCert => {
clientCertPem: String,
subjectDN: String,
issuerDN: String,
serialNumber: String,
validity: Validity
}

type Validity => {
notBefore: String,
notAfter: String
}

type Authorizer => {
jwt: JwtAuthorizer
}

type JwtAuthorizer => {
claims: Hash[Symbol, String],
scopes: Array[String]
}

type Http => {
method: String,
path: String,
protocol: String,
"?sourceIp": String,
"?userAgent": String
}
end
end
end
47 changes: 47 additions & 0 deletions lib/active_function/functions/aws_lambda/dynamodb_event.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

require "active_function_core/plugins/types"

module ActiveFunction::Functions::AwsLambda
class DynamoDBEvent
include ActiveFunctionCore::Plugins::Types

define_schema do
type Event => {
Records: Array[Record]
}

type Record => {
eventID: String,
eventVersion: String,
eventName: String,
eventSource: String,
eventSourceARN: String,
awsRegion: String,
dynamodb: StreamRecord
}

type StreamRecord => {
Keys: Hash[Symbol, AttributeValue],
SequenceNumber: String,
SizeBytes: Integer,
StreamViewType: String,
"?NewImage": Hash[Symbol, AttributeValue],
"?OldImage": Hash[Symbol, AttributeValue]
}

type AttributeValue => {
"?B": String,
"?BOOL": Boolean,
"?BS": Array[String],
"?L": Array[AttributeValue],
"?M": Hash[Symbol, AttributeValue],
"?N": String,
"?NS": Array[String],
"?NULL": Boolean,
"?S": String,
"?SS": Array[String]
}
end
end
end
Loading