Skip to content

Commit

Permalink
feat: Add basic type check without coercion (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
maxveldink authored Feb 21, 2024
1 parent 78cd681 commit 4d987e7
Show file tree
Hide file tree
Showing 39 changed files with 7,981 additions and 7,946 deletions.
12 changes: 6 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ PATH
remote: .
specs:
sorbet-schema (0.1.0)
sorbet-result (~> 1.0)
sorbet-result (~> 1.1)
sorbet-runtime (~> 0.5)
sorbet-struct-comparable (~> 1.3)
zeitwerk (~> 2.6)

GEM
Expand Down Expand Up @@ -37,14 +38,14 @@ GEM
ast (~> 2.4.1)
racc
prettier_print (1.2.1)
prism (0.21.0)
prism (0.24.0)
psych (5.1.2)
stringio
racc (1.7.3)
rainbow (3.1.1)
rake (13.1.0)
rbi (0.1.8)
prism (>= 0.18.0, < 0.22)
rbi (0.1.9)
prism (>= 0.18.0, < 0.25)
sorbet-runtime (>= 0.5.9204)
rdoc (6.6.2)
psych (>= 4.0.0)
Expand Down Expand Up @@ -73,9 +74,8 @@ GEM
ruby-progressbar (1.13.0)
sorbet (0.5.11262)
sorbet-static (= 0.5.11262)
sorbet-result (1.0.0)
sorbet-result (1.1.0)
sorbet-runtime (~> 0.5)
zeitwerk (~> 2.6)
sorbet-runtime (0.5.11262)
sorbet-static (0.5.11262-universal-darwin)
sorbet-static (0.5.11262-x86_64-linux)
Expand Down
33 changes: 0 additions & 33 deletions lib/typed/apply_validators.rb

This file was deleted.

19 changes: 4 additions & 15 deletions lib/typed/field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ module Typed
class Field < T::Struct
extend T::Sig

include T::Struct::ActsAsComparable

const :name, Symbol
const :type, T::Class[T.anything]
const :required, T::Boolean, default: true

ValidationResult = T.type_alias { Result[T.untyped, ValidationError] }

sig { returns(T::Boolean) }
def required?
required
Expand All @@ -20,20 +20,9 @@ def optional?
!required
end

sig { params(value: T.untyped).returns(ValidationResult) }
sig { params(value: T.untyped).returns(Validations::ValidationResult) }
def validate(value)
validate_required(value)
end

private

sig { params(value: T.untyped).returns(ValidationResult) }
def validate_required(value)
if required? && value.nil?
Failure.new(RequiredFieldError.new(field_name: name))
else
Success.new(value)
end
Validations::FieldTypeValidator.new.validate(field: self, value:)
end
end
end
14 changes: 9 additions & 5 deletions lib/typed/json_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ def deserialize(source)
hsh[field.name] = parsed_json[field.name.to_s]
end

ApplyValidators
.new(schema:)
.call(creation_params)
.and_then do |validated_params|
Success.new(schema.target.new(**validated_params))
results = creation_params.map do |name, value|
schema.field(name:)&.validate(value)
end.compact

Validations::ValidationResults
.new(results:)
.combine
.and_then do
Success.new(schema.target.new(**creation_params))
end
rescue JSON::ParserError
Failure.new(ParseError.new(format: :json))
Expand Down
14 changes: 0 additions & 14 deletions lib/typed/multiple_validation_error.rb

This file was deleted.

12 changes: 0 additions & 12 deletions lib/typed/required_field_error.rb

This file was deleted.

7 changes: 7 additions & 0 deletions lib/typed/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

module Typed
class Schema < T::Struct
extend T::Sig

const :fields, T::Array[Field], default: []
const :target, T.class_of(T::Struct)

sig { params(name: Symbol).returns(T.nilable(Field)) }
def field(name:)
fields.find { |field| field.name == name }
end
end
end
6 changes: 0 additions & 6 deletions lib/typed/validation_error.rb

This file was deleted.

8 changes: 8 additions & 0 deletions lib/typed/validations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# typed: strict

module Typed
module Validations
Value = T.type_alias { T.untyped }
ValidationResult = T.type_alias { Result[Value, ValidationError] }
end
end
24 changes: 24 additions & 0 deletions lib/typed/validations/field_type_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# typed: strict

module Typed
module Validations
class FieldTypeValidator
extend T::Sig

include FieldValidator

sig { override.params(field: Field, value: Value).returns(ValidationResult) }
def validate(field:, value:)
if field.type == value.class
Success.new(value)
elsif field.required? && value.nil?
Failure.new(RequiredFieldError.new(field_name: field.name))
elsif field.optional? && value.nil?
Success.new(value)
else
Failure.new(TypeMismatchError.new(field_name: field.name, field_type: field.type, given_type: value.class))
end
end
end
end
end
15 changes: 15 additions & 0 deletions lib/typed/validations/field_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# typed: strict

module Typed
module Validations
module FieldValidator
extend T::Sig
extend T::Helpers
interface!

sig { abstract.params(field: Field, value: Value).returns(ValidationResult) }
def validate(field:, value:)
end
end
end
end
16 changes: 16 additions & 0 deletions lib/typed/validations/multiple_validation_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# typed: strict

module Typed
module Validations
class MultipleValidationError < ValidationError
extend T::Sig

sig { params(errors: T::Array[ValidationError]).void }
def initialize(errors:)
combined_message = errors.map(&:message).join(" | ")

super("Multiple validation errors found: #{combined_message}")
end
end
end
end
14 changes: 14 additions & 0 deletions lib/typed/validations/required_field_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# typed: strict

module Typed
module Validations
class RequiredFieldError < ValidationError
extend T::Sig

sig { params(field_name: Symbol).void }
def initialize(field_name:)
super("#{field_name} is required.")
end
end
end
end
14 changes: 14 additions & 0 deletions lib/typed/validations/type_mismatch_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# typed: strict

module Typed
module Validations
class TypeMismatchError < ValidationError
extend T::Sig

sig { params(field_name: Symbol, field_type: T::Class[T.anything], given_type: T::Class[T.anything]).void }
def initialize(field_name:, field_type:, given_type:)
super("Invalid type given to #{field_name}. Expected #{field_type}, got #{given_type}.")
end
end
end
end
8 changes: 8 additions & 0 deletions lib/typed/validations/validation_error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# typed: strict

module Typed
module Validations
class ValidationError < DeserializeError
end
end
end
25 changes: 25 additions & 0 deletions lib/typed/validations/validation_results.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# typed: strict

module Typed
module Validations
class ValidationResults < T::Struct
extend T::Sig

const :results, T::Array[ValidationResult]

sig { returns(ValidationResult) }
def combine
failing_results = results.select(&:failure?)

case failing_results.length
when 0
Success.blank
when 1
Failure.new(T.must(failing_results.first).error)
else
Failure.new(MultipleValidationError.new(errors: failing_results.map(&:error)))
end
end
end
end
end
3 changes: 2 additions & 1 deletion sorbet-schema.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ Gem::Specification.new do |spec|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

spec.add_runtime_dependency "sorbet-result", "~> 1.0"
spec.add_runtime_dependency "sorbet-result", "~> 1.1"
spec.add_runtime_dependency "sorbet-runtime", "~> 0.5"
spec.add_runtime_dependency "sorbet-struct-comparable", "~> 1.3"
spec.add_runtime_dependency "zeitwerk", "~> 2.6"
end
26 changes: 13 additions & 13 deletions sorbet/rbi/gems/[email protected]

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 4d987e7

Please sign in to comment.