tsjson 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/lib/errors/cant_distinguish_type_error.rb +17 -0
  3. data/lib/errors/index.rb +12 -0
  4. data/lib/errors/list_validation_error.rb +34 -0
  5. data/lib/errors/literal_union_validation_error.rb +18 -0
  6. data/lib/errors/literal_validation_error.rb +16 -0
  7. data/lib/errors/not_enough_discriminators.rb +7 -0
  8. data/lib/errors/object_validation_error.rb +56 -0
  9. data/lib/errors/required_field_error.rb +7 -0
  10. data/lib/errors/scalar_union_validation_error.rb +18 -0
  11. data/lib/errors/scalar_validation_error.rb +16 -0
  12. data/lib/errors/unexpected_field_error.rb +7 -0
  13. data/lib/errors/unexpected_value_error.rb +16 -0
  14. data/lib/errors/validation_error.rb +16 -0
  15. data/lib/language/ast/kind.rb +25 -0
  16. data/lib/language/lexer/lexer.rb +452 -0
  17. data/lib/language/lexer/location.rb +20 -0
  18. data/lib/language/lexer/syntax_error.rb +89 -0
  19. data/lib/language/lexer/token.rb +34 -0
  20. data/lib/language/lexer/token_kind.rb +37 -0
  21. data/lib/language/lexer/utils.rb +32 -0
  22. data/lib/language/parser/parser.rb +437 -0
  23. data/lib/language/source.rb +109 -0
  24. data/lib/schema/schema.rb +48 -0
  25. data/lib/schema/schema_builder.rb +148 -0
  26. data/lib/tsjson.rb +1 -0
  27. data/lib/types/any.rb +15 -0
  28. data/lib/types/base.rb +19 -0
  29. data/lib/types/boolean.rb +17 -0
  30. data/lib/types/discriminator_map.rb +116 -0
  31. data/lib/types/enum.rb +47 -0
  32. data/lib/types/float.rb +17 -0
  33. data/lib/types/index.rb +27 -0
  34. data/lib/types/int.rb +17 -0
  35. data/lib/types/intersection.rb +72 -0
  36. data/lib/types/list.rb +33 -0
  37. data/lib/types/literal.rb +25 -0
  38. data/lib/types/literal_union.rb +48 -0
  39. data/lib/types/merge.rb +21 -0
  40. data/lib/types/null.rb +17 -0
  41. data/lib/types/object.rb +87 -0
  42. data/lib/types/scalar.rb +24 -0
  43. data/lib/types/scalar_union.rb +25 -0
  44. data/lib/types/string.rb +17 -0
  45. data/lib/types/union.rb +61 -0
  46. metadata +85 -0
@@ -0,0 +1,47 @@
1
+ module TSJSON
2
+ class Enum < Base
3
+ def initialize(members = {})
4
+ super()
5
+
6
+ @members = members
7
+ @members.each { |name, type| validate_type!(type) }
8
+ end
9
+
10
+ def member(name)
11
+ m = @members[name]
12
+ raise "Can't access property #{name} of #{self.class.name}" unless m
13
+
14
+ m
15
+ end
16
+ alias property member
17
+
18
+ def push_type(type)
19
+ validate_type!(type)
20
+ return self if types.include?(type)
21
+ return Enum.new(@types.dup.push(type))
22
+ end
23
+
24
+ def types
25
+ @types ||= @members.values
26
+ end
27
+
28
+ def has?(type)
29
+ types.include?(type)
30
+ end
31
+
32
+ def validate_type!(type)
33
+ unless type.is_a?(Literal)
34
+ raise "Enum can contain only literal values received #{type.class}"
35
+ end
36
+ end
37
+
38
+ def validate(value)
39
+ types.each { |type| return true if type.valid?(value) }
40
+
41
+ raise LiteralUnionValidationError.new(
42
+ expected_values: types.map(&:value),
43
+ received_value: value
44
+ )
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ require_relative './scalar.rb'
2
+
3
+ module TSJSON
4
+ class FloatType < ScalarType
5
+ def initialize
6
+ super('Float')
7
+ end
8
+
9
+ def validate(value)
10
+ super(value) unless value.is_a?(::Numeric)
11
+
12
+ true
13
+ end
14
+ end
15
+
16
+ TSJSONFloat = FloatType.new
17
+ end
@@ -0,0 +1,27 @@
1
+ require_relative '../errors/index.rb'
2
+
3
+ #
4
+
5
+ require_relative './string.rb'
6
+ require_relative './int.rb'
7
+ require_relative './float.rb'
8
+ require_relative './boolean.rb'
9
+ require_relative './null.rb'
10
+ require_relative './any.rb'
11
+
12
+ #
13
+
14
+ require_relative './literal.rb'
15
+ require_relative './scalar_union.rb'
16
+ require_relative './literal_union.rb'
17
+ require_relative './list.rb'
18
+ require_relative './object.rb'
19
+
20
+ #
21
+
22
+ require_relative './enum.rb'
23
+
24
+ #
25
+
26
+ require_relative './union.rb'
27
+ require_relative './intersection.rb'
@@ -0,0 +1,17 @@
1
+ require_relative './scalar.rb'
2
+
3
+ module TSJSON
4
+ class IntType < ScalarType
5
+ def initialize
6
+ super('Int')
7
+ end
8
+
9
+ def validate(value)
10
+ super(value) unless value.is_a?(::Integer)
11
+
12
+ true
13
+ end
14
+ end
15
+
16
+ TSJSONInt = IntType.new
17
+ end
@@ -0,0 +1,72 @@
1
+ require_relative './merge.rb'
2
+
3
+ module TSJSON
4
+ class Intersection < Merge
5
+ def normal_form
6
+ unless @normalized
7
+ @normalized =
8
+ @types.reduce(Intersection.new) do |buffer, type|
9
+ if type.is_a?(Merge)
10
+ buffer.intersect_normal(type.normal_form)
11
+ else
12
+ buffer.intersect_normal(type)
13
+ end
14
+ end
15
+ end
16
+ @normalized
17
+ end
18
+
19
+ def intersect_normal(other)
20
+ # raise_if_not_composite(other)
21
+
22
+ if other.is_a?(Intersection)
23
+ Intersection.new(@types.dup.concat(other.types), true)
24
+ elsif other.is_a?(Union)
25
+ other.types.reduce(Union.new) do |u, t|
26
+ u.union_normal(intersect_normal(t))
27
+ end
28
+ else
29
+ Intersection.new(@types.dup.push(other), true)
30
+ end
31
+ end
32
+
33
+ def union_normal(other)
34
+ # raise_if_not_composite(other)
35
+
36
+ Union.new([self, other], true)
37
+ end
38
+
39
+ def validate(object)
40
+ return normal_form.validate(object) unless @is_normal
41
+
42
+ merged_object.validate(object)
43
+ end
44
+
45
+ def fields
46
+ merged_object.fields
47
+ end
48
+
49
+ def to_s
50
+ @types.map(&:to_s).join(' & ')
51
+ end
52
+
53
+ def compile
54
+ return if @compiled
55
+ @compiled = true
56
+
57
+ return normal_form.compile unless @is_normal
58
+ merged_object
59
+ end
60
+
61
+ private
62
+
63
+ def merged_object
64
+ raise 'not a normal form' unless @is_normal
65
+
66
+ @merged_object ||=
67
+ ObjectType.new(
68
+ @types.reduce({}) { |obj, type| obj.merge(type.fields_map) }
69
+ )
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,33 @@
1
+ module TSJSON
2
+ class List < Base
3
+ def initialize(type)
4
+ super()
5
+ @type = type
6
+ end
7
+
8
+ def of_type
9
+ @type
10
+ end
11
+
12
+ def validate(value)
13
+ unless value.is_a?(::Array)
14
+ raise ScalarValidationError.new(
15
+ expected_type: 'Array',
16
+ received_type: value.class.name,
17
+ received_value: value
18
+ )
19
+ end
20
+
21
+ errors = []
22
+ value.each_with_index do |item, index|
23
+ of_type.validate(item)
24
+ rescue ValidationError => e
25
+ errors.push({ index: index, error: e })
26
+ end
27
+
28
+ raise ListValidationError.new(errors: errors) if errors.length > 0
29
+
30
+ true
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,25 @@
1
+ module TSJSON
2
+ class Literal < Base
3
+ attr_reader :value
4
+
5
+ def initialize(value)
6
+ @value = value
7
+ end
8
+
9
+ def ==(other)
10
+ return value == other unless other.is_a?(self.class)
11
+
12
+ value == other.value
13
+ end
14
+
15
+ def validate(other)
16
+ unless @value == other
17
+ raise LiteralValidationError.new(
18
+ expected_value: @value, received_value: other
19
+ )
20
+ end
21
+
22
+ true
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,48 @@
1
+ module TSJSON
2
+ class LiteralUnion < Base
3
+ attr_reader :types
4
+
5
+ def initialize(types = [])
6
+ types.each { |t| validate_type!(t) }
7
+
8
+ @types = types
9
+ end
10
+
11
+ def push_type(type)
12
+ validate_type!(type)
13
+ return self if types.include?(type)
14
+ return LiteralUnion.new(@types.dup.push(type))
15
+ end
16
+
17
+ def has?(type)
18
+ @types.include?(type)
19
+ end
20
+
21
+ def size
22
+ @types.size
23
+ end
24
+
25
+ def validate_type!(type)
26
+ unless type.is_a?(Literal)
27
+ raise 'LiteralUnion may contain only Literal types'
28
+ end
29
+ end
30
+
31
+ def validate(value)
32
+ @types.each { |type| return true if type.valid?(value) }
33
+
34
+ raise LiteralUnionValidationError.new(
35
+ expected_values: @types.map(&:value),
36
+ received_value: value
37
+ )
38
+ end
39
+
40
+ def to_s
41
+ @types.map(&:to_s).join(' | ')
42
+ end
43
+
44
+ def to_json
45
+ @types.map(&:value)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,21 @@
1
+ module TSJSON
2
+ class Merge < Base
3
+ attr_reader :types
4
+ attr_accessor :is_normal
5
+
6
+ def initialize(types = [], is_normal = false)
7
+ super()
8
+
9
+ types.each do |t|
10
+ unless t.is_a?(ObjectType) || t.is_a?(Union) || t.is_a?(Intersection)
11
+ raise "#{
12
+ self.class.name
13
+ } may contain only ObjectType, Union or Intersection types"
14
+ end
15
+ end
16
+
17
+ @types = types
18
+ @is_normal = types.length == 0 || is_normal
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ require_relative './scalar.rb'
2
+
3
+ module TSJSON
4
+ class NullType < ScalarType
5
+ def initialize
6
+ super('Null')
7
+ end
8
+
9
+ def validate(value)
10
+ super(value) unless value == nil
11
+
12
+ true
13
+ end
14
+ end
15
+
16
+ TSJSONNull = NullType.new
17
+ end
@@ -0,0 +1,87 @@
1
+ module TSJSON
2
+ class ObjectType < Base
3
+ def initialize(fields_map = nil, &block)
4
+ super()
5
+ @fields_map = fields_map&.transform_keys!(&:to_s)
6
+ @resolve_fields = block
7
+ end
8
+
9
+ def fields
10
+ @fields = fields_map.map { |k, v| v.merge({ name: k }) } unless @fields
11
+
12
+ @fields
13
+ end
14
+
15
+ def field(name)
16
+ f = fields_map.dig(name, :type)
17
+ raise "Can't access index #{name} of #{self.class.name}" unless f
18
+
19
+ f
20
+ end
21
+ alias index field
22
+
23
+ def fields_map
24
+ unless @fields_map
25
+ @fields_map = @resolve_fields.call&.transform_keys!(&:to_s)
26
+ end
27
+ @fields_map
28
+ end
29
+
30
+ def compile
31
+ return if @compiled
32
+ @compiled = true
33
+
34
+ fields.each { |f| f[:type].compile }
35
+ end
36
+
37
+ def validate(object)
38
+ unless object.is_a?(::Hash)
39
+ raise ScalarValidationError.new(
40
+ expected_type: 'Object',
41
+ received_type: object.class.name,
42
+ received_value: object
43
+ )
44
+ end
45
+
46
+ object = object&.transform_keys!(&:to_s)
47
+
48
+ keys = object.keys
49
+ errors = []
50
+
51
+ fields.each do |f|
52
+ name = f[:name]
53
+ type = f[:type]
54
+ optional = f[:optional]
55
+ value = object[name]
56
+
57
+ keys.delete(name)
58
+ has_field = object.key?(name)
59
+ next if optional && !has_field
60
+ raise RequiredFieldError.new if !optional && !has_field
61
+
62
+ type.validate(value)
63
+ rescue ValidationError => e
64
+ errors.push({ field: name, error: e })
65
+ end
66
+ keys.each do |name|
67
+ errors.push({ field: name, error: UnexpectedFieldError.new })
68
+ end
69
+
70
+ raise ObjectValidationError.new(errors: errors) if errors.length > 0
71
+
72
+ true
73
+ end
74
+
75
+ def to_s
76
+ "{\n#{
77
+ fields.map do |f|
78
+ name = f[:name]
79
+ type = f[:type].to_s
80
+ optional = f[:optional]
81
+
82
+ " #{name}#{optional ? '?' : ''}: #{type}"
83
+ end.join("\n")
84
+ }\n}"
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,24 @@
1
+ require_relative './base.rb'
2
+
3
+ module TSJSON
4
+ class ScalarType < Base
5
+ attr_reader :name
6
+
7
+ def initialize(name)
8
+ super()
9
+ @name = name
10
+ end
11
+
12
+ def validate(value)
13
+ raise ScalarValidationError.new(
14
+ expected_type: @name,
15
+ received_type: value.class.name,
16
+ received_value: value
17
+ )
18
+ end
19
+
20
+ def to_s
21
+ @name
22
+ end
23
+ end
24
+ end