Skip to content

Commit

Permalink
TypeCast WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
yosiat committed Oct 24, 2024
1 parent c4fe37a commit 654db93
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 64 deletions.
13 changes: 13 additions & 0 deletions ext/panko_serializer/attributes_writer/type_cast/type_cast.c
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,14 @@ VALUE public_type_cast(int argc, VALUE* argv, VALUE self) {
return type_cast(type_metadata, value, &isJson);
}

VALUE public_is_iso8601_time_string(VALUE klass, VALUE value) {
return is_iso8601_time_string(StringValuePtr(value)) ? Qtrue : Qfalse;
}

VALUE public_iso_ar_iso_datetime_string(VALUE klass, VALUE value) {
return iso_ar_iso_datetime_string(StringValuePtr(value));
}

void panko_init_type_cast(VALUE mPanko) {
to_s_id = rb_intern("to_s");
to_i_id = rb_intern("to_i");
Expand All @@ -375,6 +383,11 @@ void panko_init_type_cast(VALUE mPanko) {
// TODO: pass 3 arguments here
rb_define_singleton_method(mPanko, "_type_cast", public_type_cast, -1);

rb_define_singleton_method(mPanko, "is_iso8601_time_string",
public_is_iso8601_time_string, 1);
rb_define_singleton_method(mPanko, "iso_ar_iso_datetime_string",
public_iso_ar_iso_datetime_string, 1);

panko_init_time(mPanko);

rb_global_variable(&oj_type);
Expand Down
3 changes: 2 additions & 1 deletion lib/panko/impl/attributes_writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ def read_attribute(attribute)
if !attribute.type.nil? && !value.nil?
# TODO: handle is_json
is_json = false
return Panko._type_cast(attribute.type, value, is_json)

return TypeCast.public_type_cast(attribute.type, value, is_json)
end

value
Expand Down
210 changes: 210 additions & 0 deletions lib/panko/impl/type_cast.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
module Panko::Impl
class TypeCast
@@to_s_id = nil
@@to_i_id = nil

# Caching ActiveRecord Types
@@ar_string_type = nil
@@ar_text_type = nil
@@ar_float_type = nil
@@ar_integer_type = nil
@@ar_boolean_type = nil
@@ar_date_time_type = nil
@@ar_json_type = nil
@@ar_pg_integer_type = nil
@@ar_pg_float_type = nil
@@ar_pg_uuid_type = nil
@@ar_pg_json_type = nil
@@ar_pg_jsonb_type = nil
@@ar_pg_array_type = nil
@@ar_pg_date_time_type = nil
@@ar_pg_timestamp_type = nil

@@initialized = false

def self.cache_postgres_type_lookup
ar_connection_adapters = ActiveRecord::ConnectionAdapters if defined?(ActiveRecord::ConnectionAdapters)

return false unless ar_connection_adapters

case ActiveRecord::Base.connection.adapter_name
when "PostgreSQL"
adapter_module = ar_connection_adapters::PostgreSQL if defined?(ar_connection_adapters::PostgreSQL)
return false unless adapter_module

ar_oid = adapter_module::OID if defined?(adapter_module::OID)
return false unless ar_oid

@@ar_pg_float_type = ar_oid::Float if defined?(ar_oid::Float)
@@ar_pg_integer_type = ar_oid::Integer if defined?(ar_oid::Integer)
@@ar_pg_uuid_type = ar_oid::Uuid if defined?(ar_oid::Uuid)
@@ar_pg_json_type = ar_oid::Json if defined?(ar_oid::Json)
@@ar_pg_jsonb_type = ar_oid::Jsonb if defined?(ar_oid::Jsonb)
@@ar_pg_date_time_type = ar_oid::DateTime if defined?(ar_oid::DateTime)
@@ar_pg_timestamp_type = ar_oid::Timestamp if defined?(ar_oid::Timestamp)
when "SQLite"
adapter_module = ar_connection_adapters::SQLite3Adapter if defined?(ar_connection_adapters::SQLite3Adapter)
return false unless adapter_module

@@ar_pg_integer_type = adapter_module::SQLite3Integer if defined?(adapter_module::SQLite3Integer)
end

true
end

def self.cache_type_lookup
return if @@initialized

@@initialized = true

if defined?(ActiveRecord::Type)
@@ar_string_type = ActiveRecord::Type::String if defined?(ActiveRecord::Type::String)
@@ar_text_type = ActiveRecord::Type::Text if defined?(ActiveRecord::Type::Text)
@@ar_float_type = ActiveRecord::Type::Float if defined?(ActiveRecord::Type::Float)
@@ar_integer_type = ActiveRecord::Type::Integer if defined?(ActiveRecord::Type::Integer)
@@ar_boolean_type = ActiveRecord::Type::Boolean if defined?(ActiveRecord::Type::Boolean)
@@ar_date_time_type = ActiveRecord::Type::DateTime if defined?(ActiveRecord::Type::DateTime)

@@ar_json_type = ActiveRecord::Type::Json if defined?(ActiveRecord::Type::Json)
end

begin
cache_postgres_type_lookup
rescue
# TODO: add to some debug log
end
end

def self.is_string_or_text_type(type_klass)
type_klass == @@ar_string_type || type_klass == @@ar_text_type ||
(@@ar_pg_uuid_type && type_klass == @@ar_pg_uuid_type)
end

def self.cast_string_or_text_type(value)
return value if value.is_a?(String)
return "t" if value == true
return "f" if value == false

value.to_s
end

def self.is_float_type(type_klass)
type_klass == @@ar_float_type || (@@ar_pg_float_type && type_klass == @@ar_pg_float_type)
end

def self.cast_float_type(value)
return value if value.is_a?(Float)
return value.to_f if value.is_a?(String)

nil
end

def self.is_integer_type(type_klass)
type_klass == @@ar_integer_type || (@@ar_pg_integer_type && type_klass == @@ar_pg_integer_type)
end

def self.cast_integer_type(value)
return value if value.is_a?(Integer)
return value.to_i if value.is_a?(String) && !value.empty?
return value.to_i if value.is_a?(Float)
return 1 if value == true
return 0 if value == false

nil
end

def self.is_json_type(type_klass)
@@ar_pg_json_type && type_klass == @@ar_pg_json_type ||
@@ar_pg_jsonb_type && type_klass == @@ar_pg_jsonb_type ||
@@ar_json_type && type_klass == @@ar_json_type
end

def self.is_json_value(value)
return value unless value.is_a?(String)

return false if value.length == 0

begin
result = Oj.sc_parse(Object.new, value)

return true if result.nil?
return false if result == false
rescue Oj::ParseError
return false
end

false
end

def self.is_boolean_type(type_klass)
type_klass == @@ar_boolean_type
end

def self.cast_boolean_type(value)
return value if value == true || value == false
return nil if value.nil?

if value.is_a?(String)
return nil if value.length == 0

is_false_value =
value == "0" || (value == "f" || value == "F") ||
(value.downcase == "false" || value.downcase == "off")

return is_false_value ? true : false
end

if value.is_a?(Integer)
return value == 1
end

nil
end

def self.is_date_time_type(type_klass)
type_klass == @@ar_date_time_type ||
type_klass == @@ar_pg_date_time_type ||
type_klass == @@ar_pg_timestamp_type
end

def self.cast_date_time_type(value)
if value.is_a?(String)
return value if value.end_with?("Z") && Panko.is_iso8601_time_string(value) # TODO: Implement `is_iso8601_time_string`
iso8601_string = Panko.iso_ar_iso_datetime_string(value) # TODO: Implement `iso_ar_iso_datetime_string`
return iso8601_string unless iso8601_string.nil?
end

nil
end

def self.public_type_cast(type_metadata, value, is_json = false)
return value if value.nil?

cache_type_lookup

type_klass = type_metadata.class
type_casted_value = nil

if is_string_or_text_type(type_klass)
type_casted_value = cast_string_or_text_type(value)
elsif is_integer_type(type_klass)
type_casted_value = cast_integer_type(value)
elsif is_boolean_type(type_klass)
type_casted_value = cast_boolean_type(value)
elsif is_date_time_type(type_klass)
type_casted_value = cast_date_time_type(value)
elsif is_float_type(type_klass)
type_casted_value = cast_float_type(value)
end

if is_json_type(type_klass)
return nil if is_json_value(value) == false
return value
end

return type_metadata.deserialize(value) if type_casted_value.nil?

type_casted_value
end
end
end
1 change: 1 addition & 0 deletions lib/panko_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "panko/version"
require "panko/impl/type_cast"
require "panko/impl/attributes_writer"
require "panko/impl/serializer"
require "panko/attribute"
Expand Down
Loading

0 comments on commit 654db93

Please sign in to comment.