Class: AnyVali::Schema
- Inherits:
-
Object
- Object
- AnyVali::Schema
- Defined in:
- lib/anyvali/schema.rb
Direct Known Subclasses
AnySchema, ArraySchema, BoolSchema, EnumSchema, IntersectionSchema, LiteralSchema, NeverSchema, NullSchema, NullableSchema, NumberSchema, ObjectSchema, OptionalSchema, RecordSchema, RefSchema, StringSchema, TupleSchema, UnionSchema, UnknownSchema
Constant Summary collapse
- RESERVED_METADATA_KEYS =
%w[title description deprecated deprecatedMessage notStable since sensitive readonly writeonly examples].freeze
Instance Attribute Summary collapse
-
#coerce_config ⇒ Object
readonly
Returns the value of attribute coerce_config.
-
#constraints ⇒ Object
readonly
Returns the value of attribute constraints.
-
#custom_validators ⇒ Object
readonly
Returns the value of attribute custom_validators.
-
#default_value ⇒ Object
readonly
Returns the value of attribute default_value.
-
#has_default ⇒ Object
readonly
Returns the value of attribute has_default.
-
#kind ⇒ Object
readonly
Returns the value of attribute kind.
-
#metadata ⇒ Object
readonly
Returns the value of attribute metadata.
Class Method Summary collapse
-
.type_name(value) ⇒ Object
Helper to get the JSON type name for a Ruby value.
Instance Method Summary collapse
- #coerce(config) ⇒ Object
- #default(value) ⇒ Object
- #describe(description, **opts) ⇒ Object
- #export(mode: :portable) ⇒ Object
-
#initialize(kind:, constraints: {}, coerce_config: nil, default_value: nil, has_default: false, custom_validators: [], metadata: {}) ⇒ Schema
constructor
A new instance of Schema.
- #parse(input) ⇒ Object
- #portable? ⇒ Boolean
- #refine(&block) ⇒ Object
- #safe_parse(input, path: [], context: nil) ⇒ Object
- #to_node ⇒ Object
- #with_metadata(meta, replace: false) ⇒ Object
Constructor Details
#initialize(kind:, constraints: {}, coerce_config: nil, default_value: nil, has_default: false, custom_validators: [], metadata: {}) ⇒ Schema
Returns a new instance of Schema.
10 11 12 13 14 15 16 17 18 |
# File 'lib/anyvali/schema.rb', line 10 def initialize(kind:, constraints: {}, coerce_config: nil, default_value: nil, has_default: false, custom_validators: [], metadata: {}) @kind = kind @constraints = constraints.freeze @coerce_config = coerce_config @default_value = default_value @has_default = has_default @custom_validators = custom_validators.freeze @metadata = .freeze end |
Instance Attribute Details
#coerce_config ⇒ Object (readonly)
Returns the value of attribute coerce_config.
5 6 7 |
# File 'lib/anyvali/schema.rb', line 5 def coerce_config @coerce_config end |
#constraints ⇒ Object (readonly)
Returns the value of attribute constraints.
5 6 7 |
# File 'lib/anyvali/schema.rb', line 5 def constraints @constraints end |
#custom_validators ⇒ Object (readonly)
Returns the value of attribute custom_validators.
5 6 7 |
# File 'lib/anyvali/schema.rb', line 5 def custom_validators @custom_validators end |
#default_value ⇒ Object (readonly)
Returns the value of attribute default_value.
5 6 7 |
# File 'lib/anyvali/schema.rb', line 5 def default_value @default_value end |
#has_default ⇒ Object (readonly)
Returns the value of attribute has_default.
5 6 7 |
# File 'lib/anyvali/schema.rb', line 5 def has_default @has_default end |
#kind ⇒ Object (readonly)
Returns the value of attribute kind.
5 6 7 |
# File 'lib/anyvali/schema.rb', line 5 def kind @kind end |
#metadata ⇒ Object (readonly)
Returns the value of attribute metadata.
5 6 7 |
# File 'lib/anyvali/schema.rb', line 5 def @metadata end |
Class Method Details
.type_name(value) ⇒ Object
Helper to get the JSON type name for a Ruby value
188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/anyvali/schema.rb', line 188 def self.type_name(value) case value when NilClass then "null" when TrueClass, FalseClass then "boolean" when Integer then "number" when Float then "number" when String then "string" when Array then "array" when Hash then "object" else value.class.name.downcase end end |
Instance Method Details
#coerce(config) ⇒ Object
69 70 71 |
# File 'lib/anyvali/schema.rb', line 69 def coerce(config) dup_with(coerce_config: config) end |
#default(value) ⇒ Object
65 66 67 |
# File 'lib/anyvali/schema.rb', line 65 def default(value) dup_with(default_value: value, has_default: true) end |
#describe(description, **opts) ⇒ Object
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/anyvali/schema.rb', line 73 def describe(description, **opts) raise ArgumentError, "describe(): description must be a string" unless description.is_a?(String) = { "description" => description } if opts.key?(:title) raise ArgumentError, "describe(): title must be a string" unless opts[:title].is_a?(String) ["title"] = opts[:title] end if opts.key?(:deprecated) raise ArgumentError, "describe(): deprecated must be a boolean" unless [true, false].include?(opts[:deprecated]) ["deprecated"] = opts[:deprecated] end if opts.key?(:deprecated_message) raise ArgumentError, "describe(): deprecatedMessage must be a string" unless opts[:deprecated_message].is_a?(String) raise ArgumentError, "describe(): deprecatedMessage requires deprecated: true" unless opts[:deprecated] ["deprecatedMessage"] = opts[:deprecated_message] end if opts.key?(:not_stable) raise ArgumentError, "describe(): notStable must be a boolean" unless [true, false].include?(opts[:not_stable]) ["notStable"] = opts[:not_stable] end if opts.key?(:since) raise ArgumentError, "describe(): since must be a string" unless opts[:since].is_a?(String) ["since"] = opts[:since] end if opts.key?(:sensitive) raise ArgumentError, "describe(): sensitive must be a boolean" unless [true, false].include?(opts[:sensitive]) ["sensitive"] = opts[:sensitive] end if opts.key?(:readonly) raise ArgumentError, "describe(): readonly must be a boolean" unless [true, false].include?(opts[:readonly]) ["readonly"] = opts[:readonly] end if opts.key?(:writeonly) raise ArgumentError, "describe(): writeonly must be a boolean" unless [true, false].include?(opts[:writeonly]) ["writeonly"] = opts[:writeonly] end if opts[:readonly] && opts[:writeonly] raise ArgumentError, "describe(): readonly and writeonly cannot both be true" end if opts.key?(:examples) raise ArgumentError, "describe(): examples must be an array" unless opts[:examples].is_a?(Array) ["examples"] = opts[:examples] end dup_with(metadata: @metadata.merge()) end |
#export(mode: :portable) ⇒ Object
145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/anyvali/schema.rb', line 145 def export(mode: :portable) if mode == :portable && !portable? raise ValidationError, [ ValidationIssue.new( code: IssueCodes::CUSTOM_VALIDATION_NOT_PORTABLE, expected: "portable schema", received: "schema with custom validators" ) ] end doc = AnyValiDocument.new(root: self) doc.to_h end |
#parse(input) ⇒ Object
20 21 22 23 24 |
# File 'lib/anyvali/schema.rb', line 20 def parse(input) result = safe_parse(input) raise ValidationError, result.issues if result.failure? result.value end |
#portable? ⇒ Boolean
141 142 143 |
# File 'lib/anyvali/schema.rb', line 141 def portable? @custom_validators.empty? end |
#refine(&block) ⇒ Object
137 138 139 |
# File 'lib/anyvali/schema.rb', line 137 def refine(&block) dup_with(custom_validators: @custom_validators + [block]) end |
#safe_parse(input, path: [], context: nil) ⇒ Object
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/anyvali/schema.rb', line 26 def safe_parse(input, path: [], context: nil) context ||= ValidationContext.new value = input issues = [] # Step 1: Coercion (if present and configured) if @coerce_config && !value.nil? coerced = Coercion.apply(value, @coerce_config, @kind) if coerced[:success] value = coerced[:value] else issues << ValidationIssue.new( code: IssueCodes::COERCION_FAILED, path: path, expected: @kind, received: value.is_a?(String) ? value : value.to_s ) return ParseResult.new(value: nil, issues: issues) end end # Step 2: Validate validate(value, path, issues, context) # Step 3: Custom validators if issues.empty? && !@custom_validators.empty? @custom_validators.each do |validator| validator_issues = validator.call(value, path) issues.concat(validator_issues) if validator_issues end end if issues.empty? ParseResult.new(value: value, issues: []) else ParseResult.new(value: nil, issues: issues) end end |
#to_node ⇒ Object
159 160 161 162 163 164 165 166 |
# File 'lib/anyvali/schema.rb', line 159 def to_node node = { "kind" => @kind } @constraints.each { |k, v| node[k] = v } node["coerce"] = @coerce_config if @coerce_config node["default"] = @default_value if @has_default node["metadata"] = @metadata.dup unless @metadata.empty? node end |
#with_metadata(meta, replace: false) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/anyvali/schema.rb', line 122 def (, replace: false) .each_key do |key| if RESERVED_METADATA_KEYS.include?(key) raise ArgumentError, "with_metadata(): \"#{key}\" is a reserved key. Use describe() instead." end end if replace preserved = @metadata.select { |k, _| RESERVED_METADATA_KEYS.include?(k) } dup_with(metadata: preserved.merge()) else dup_with(metadata: @metadata.merge()) end end |