jsonapi-suite / jsonapi_compliable

MIT License
20 stars 35 forks source link

Add type checking/coercion to filters and reads #113

Closed richmolj closed 6 years ago

richmolj commented 6 years ago

This adds dry-types as a dependency to do runtime type-checking of serialized response and filters. It will first attempt to coerce the value, then raise an error if it cannot coerce or is still the wrong time post-coercion.

Each attribute 'type' is really 4 different types: what we want for reads, writes, filters, and tests. For example, filters should not accept null by default, but serialization should accept null. For tests, we want to assert the final value of DateTime is an iso8601 string, but for serialization we just want DateTime.

The best way to see the behavior of individual types is spec/serialization_spec.rb and spec/filtering_spec.rb. The general logic is "lean towards strict, and coerce only as a 'nice to have'".

One issue is that there isn't a common error class. dry-types itself will sometimes raise ConstraintError, ArgumentError, or TypeError. While we could rescue these and raise a general exception, it would come at the cost of hiding real errors for custom types and add a level of indirection when debugging. So I have chosen to leave as-is for now.

 # Default types
attribute :foo, :string
attribute :foo, :integer
 # BigDecimal vs Float
 # Note json decimals must be strings to preserve precision!
attribute :foo, :decimal
attribute :foo, :float
attribute :foo, :boolean
attribute :foo, :date
attribute :foo, :datetime
attribute :foo, :hash
attribute :foo, :array

 # All of these can be arrays
attribute :foo, :array_of_strings
attribute :foo, :array_of_floats

Override types:

JsonapiCompliable::Types[:integer] = {
  read: MyType,
  write: MyType,
  params: MyType,
  test: MyType
}

Provide custom types:

StatusType = Types::Strict::String.enum('draft', 'published', 'archived')
JsonapiCompliable::Types[:status] = StatusType # use for all
  # or
JsonapiCompliable::Types[:status] = { read: StatusType, ... }

dry-types supports hash schemas as well for nested json attributes:

meta = Types::Hash.schema({
  foo: Dry::Types['strict.string'],
  bar: Dry::Types['strict.integer']
})
JsonapiCompliable::Types[:meta] = meta

attribute :foo, :meta
richmolj commented 6 years ago

@wadetandy on top of https://github.com/jsonapi-suite/jsonapi_compliable/pull/112

wadetandy commented 6 years ago

Note json decimals must be strings to preserve precision!

True for integers as well.