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.
- checksums.yaml +7 -0
- data/lib/errors/cant_distinguish_type_error.rb +17 -0
- data/lib/errors/index.rb +12 -0
- data/lib/errors/list_validation_error.rb +34 -0
- data/lib/errors/literal_union_validation_error.rb +18 -0
- data/lib/errors/literal_validation_error.rb +16 -0
- data/lib/errors/not_enough_discriminators.rb +7 -0
- data/lib/errors/object_validation_error.rb +56 -0
- data/lib/errors/required_field_error.rb +7 -0
- data/lib/errors/scalar_union_validation_error.rb +18 -0
- data/lib/errors/scalar_validation_error.rb +16 -0
- data/lib/errors/unexpected_field_error.rb +7 -0
- data/lib/errors/unexpected_value_error.rb +16 -0
- data/lib/errors/validation_error.rb +16 -0
- data/lib/language/ast/kind.rb +25 -0
- data/lib/language/lexer/lexer.rb +452 -0
- data/lib/language/lexer/location.rb +20 -0
- data/lib/language/lexer/syntax_error.rb +89 -0
- data/lib/language/lexer/token.rb +34 -0
- data/lib/language/lexer/token_kind.rb +37 -0
- data/lib/language/lexer/utils.rb +32 -0
- data/lib/language/parser/parser.rb +437 -0
- data/lib/language/source.rb +109 -0
- data/lib/schema/schema.rb +48 -0
- data/lib/schema/schema_builder.rb +148 -0
- data/lib/tsjson.rb +1 -0
- data/lib/types/any.rb +15 -0
- data/lib/types/base.rb +19 -0
- data/lib/types/boolean.rb +17 -0
- data/lib/types/discriminator_map.rb +116 -0
- data/lib/types/enum.rb +47 -0
- data/lib/types/float.rb +17 -0
- data/lib/types/index.rb +27 -0
- data/lib/types/int.rb +17 -0
- data/lib/types/intersection.rb +72 -0
- data/lib/types/list.rb +33 -0
- data/lib/types/literal.rb +25 -0
- data/lib/types/literal_union.rb +48 -0
- data/lib/types/merge.rb +21 -0
- data/lib/types/null.rb +17 -0
- data/lib/types/object.rb +87 -0
- data/lib/types/scalar.rb +24 -0
- data/lib/types/scalar_union.rb +25 -0
- data/lib/types/string.rb +17 -0
- data/lib/types/union.rb +61 -0
- metadata +85 -0
data/lib/types/enum.rb
ADDED
@@ -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
|
data/lib/types/float.rb
ADDED
@@ -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
|
data/lib/types/index.rb
ADDED
@@ -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'
|
data/lib/types/int.rb
ADDED
@@ -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
|
data/lib/types/list.rb
ADDED
@@ -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
|
data/lib/types/merge.rb
ADDED
@@ -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
|
data/lib/types/null.rb
ADDED
data/lib/types/object.rb
ADDED
@@ -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
|
data/lib/types/scalar.rb
ADDED
@@ -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
|