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 = "string") ⇒ Object
Enable coercion on this schema.
- #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
236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/anyvali/schema.rb', line 236 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 = "string") ⇒ Object
Enable coercion on this schema.
With no argument (schema.coerce) the only portable coercion source —
"string" (spec 5.1) — is implied, and the coercion target is inferred
from the schema kind (e.g. number().coerce coerces a string to a number).
An explicit config string/array (e.g. "string->int", "trim",
["trim", "lower"]) is still accepted for backwards compatibility.
78 79 80 |
# File 'lib/anyvali/schema.rb', line 78 def coerce(config = "string") dup_with(coerce_config: config) end |
#default(value) ⇒ Object
67 68 69 |
# File 'lib/anyvali/schema.rb', line 67 def default(value) dup_with(default_value: value, has_default: true) end |
#describe(description, **opts) ⇒ Object
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 121 122 123 124 125 126 127 128 129 |
# File 'lib/anyvali/schema.rb', line 82 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
154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/anyvali/schema.rb', line 154 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
150 151 152 |
# File 'lib/anyvali/schema.rb', line 150 def portable? @custom_validators.empty? end |
#refine(&block) ⇒ Object
146 147 148 |
# File 'lib/anyvali/schema.rb', line 146 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 64 65 |
# File 'lib/anyvali/schema.rb', line 26 def safe_parse(input, path: [], context: nil) context ||= ValidationContext.new handled, sensitive = handle_sensitive(input, path, context) return sensitive if handled 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
168 169 170 171 172 173 174 175 |
# File 'lib/anyvali/schema.rb', line 168 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
131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/anyvali/schema.rb', line 131 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 |