maxveldink / sorbet-schema Goto Github PK
View Code? Open in Web Editor NEWExtendable serialization and deserialization to various formats for Sorbet T::Structs
License: MIT License
Extendable serialization and deserialization to various formats for Sorbet T::Structs
License: MIT License
When we ApplyValidators
, we should check the field type against the given value. For now, we won't do any coercion and simply return validation issues that we see.
We have support for adding a serializer to a field to change its serialization behavior; it would be nice to be able to set a serializer based on a type that would be used for any matching type in a schema.
Right now, you'll get a hard Sorbet type error if you try to create a Schema from a Struct with a T::Boolean
field. We should support these and handle coercion.
Allow for arrays to be defined as types in fields
Currently, when you deserialize into a struct, we need to do casting if we use the result in a chain:
CreateEntityRequest
.deserialize_from(:hash, entity_params.to_h)
.and_then { |create_entity_request| CreateEntity.call(T.cast(create_entity_request, CreateEntityRequest)) }
This is because the deserialize result payload is a T::Struct
but we can't guarantee the type. We should be able to fix this with a Tapioca compiler that adds more specific RBIs for each T::Struct present in your application.
If we have a custom type that we'd like to support for fields, we need to give consumers a way to register a type coercion setting.
I didn't have access to make a branch and I got lazy so going to make this issue just so you have something to noodle on tomorrow if you're bored.
Sorbet-schema can be used to deserialize data into a struct with a custom coercer.
struct Example
const :country, Country
It can also be used to deserialize data into a struct with a nested struct.
struct Nested
const :name, String
struct Example
const :nested, Nested
It doesn't work though when you want to deserialize data into a struct with a nested struct that has a custom coercer.
struct Nested
const :country, Country
struct Example
const :nested, Nested
With this last example, it'll still create the Example struct with the Nested struct correctly created but country will not be coerced, it'll just be the string representation I passed as json. This is because the nested struct will get converted using the Struct coercer and this will just not consider any custom coercers https://github.com/maxveldink/sorbet-schema/blob/main/lib/typed/coercion/struct_coercer.rb#L24.
Here's a first draft at how I got it working but I'm sure there's a better way to do it.
sig { override.params(type: T::Types::Base, value: Value).returns(Result[Target, CoercionError]) }
def coerce(type:, value:)
return Failure.new(CoercionError.new("Field type must inherit from T::Struct for Struct coercion.")) unless used_for_type?(type)
return Success.new(value) if type.recursively_valid?(value)
return Failure.new(CoercionError.new("Value of type '#{value.class}' cannot be coerced to #{type} Struct.")) unless value.is_a?(Hash)
values = {}
type.raw_type.props.each do |name, prop|
if prop[:type_object].try(:raw_type).respond_to?(:props)
Typed::HashSerializer
.new(schema: Typed::Schema.from_struct(prop[:type_object].raw_type))
.deserialize(value[name])
.and_then { |struct| Typed::Success.new(values[name] = struct) }
.on_error { |error| return Failure.new(CoercionError.new("Nested hash for #{type} could not be coerced to #{name}")) }
else
Coercion
.coerce(type: prop[:type_object], value: value[name])
.and_then { |coerced_value| Typed::Success.new(values[name] = struct) }
.on_error { |error| return Failure.new(CoercionError.new("Value for #{name} could not be coerced to #{prop[:type]}")) }
end
end
Success.new(type.raw_type.new(values))
rescue ArgumentError, RuntimeError
Failure.new(CoercionError.new("Given hash could not be coerced to #{type}."))
end
Goal: Be able to build a struct from another struct even if the field names are different
Context: Serializing a struct currently always makes the keys the name of the fields, this makes this flow not work if you want to build one struct from another:
income_type
type
step 3 will raise an error Typed::Validations::RequiredFieldError: type is required.
because the hash doesn't have a key for type
, but it has a key for `income_type.
Ideally, it'd be great to specify a target for each field. If target is specified, that's what is used for the key name instead of the field name.
For our first foray into type coercion, we should choose simple types. We also need to return errors during coercion.
Conventionally, we expect a Struct
to define a .schema
class method for it's schema (a default is implemented on all of them via struct_ext.rb
.) We should defer to this when we're doing struct serialization as well
We should allow Fields
to receive a coercion proc that can be used instead of our default coercion behavior.
these errors are not obvious to debug:
Failed to deserialize response: Typed::Coercion::CoercionNotSupportedError
Adding the field name, source and destination types, and what was the value that was used
While running the test suite, we see the following warnings a few times:
(eval at /Users/maxveldink/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/sorbet-runtime-0.5.11284/lib/types/props/has_lazily_specialized_methods.rb:65):1: warning: method redefined; discarding old __t_props_generated_deserialize
/Users/maxveldink/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/sorbet-runtime-0.5.11284/lib/types/props/has_lazily_specialized_methods.rb:91: warning: previous definition of __t_props_generated_deserialize was here
I'm not quite sure why we're redefining methods, but we should try and suppress this warning.
Currently, when someone pulls in sorbet-schema and rebuilds the RBIs, the T::Struct extensions aren't detected by Tapioca and added to the RBIs. Further, the deserialize_to
extension method on struct returns a T::Struct
in the success position, which needs to be cast by the caller. We probably need a compiler that detects structs in the project and adds custom RBIs for these.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.