jsonschema_rs 0.42.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.
data/src/error_kind.rs ADDED
@@ -0,0 +1,302 @@
1
+ //! ValidationErrorKind enum for Ruby.
2
+ use magnus::{
3
+ gc, method,
4
+ prelude::*,
5
+ value::{Opaque, StaticSymbol},
6
+ Error, RModule, Ruby, TypedData, Value,
7
+ };
8
+
9
+ use crate::{ser::value_to_ruby, static_id::define_rb_intern};
10
+
11
+ define_rb_intern!(static ID_NAME: "name");
12
+ define_rb_intern!(static ID_VALUE: "value");
13
+ define_rb_intern!(static ID_LIMIT: "limit");
14
+ define_rb_intern!(static ID_UNEXPECTED: "unexpected");
15
+ define_rb_intern!(static ID_CONTEXT: "context");
16
+ define_rb_intern!(static ID_ERROR: "error");
17
+ define_rb_intern!(static ID_EXPECTED_VALUE: "expected_value");
18
+ define_rb_intern!(static ID_CONTENT_ENCODING: "content_encoding");
19
+ define_rb_intern!(static ID_CONTENT_MEDIA_TYPE: "content_media_type");
20
+ define_rb_intern!(static ID_MESSAGE: "message");
21
+ define_rb_intern!(static ID_OPTIONS: "options");
22
+ define_rb_intern!(static ID_FORMAT: "format");
23
+ define_rb_intern!(static ID_MULTIPLE_OF: "multiple_of");
24
+ define_rb_intern!(static ID_SCHEMA: "schema");
25
+ define_rb_intern!(static ID_REASON: "reason");
26
+ define_rb_intern!(static ID_PROPERTY: "property");
27
+ define_rb_intern!(static ID_TYPES: "types");
28
+ define_rb_intern!(static ID_PATTERN: "pattern");
29
+ define_rb_intern!(static ID_CTX_INSTANCE_PATH: "instance_path");
30
+ define_rb_intern!(static ID_CTX_SCHEMA_PATH: "schema_path");
31
+ define_rb_intern!(static ID_CTX_EVALUATION_PATH: "evaluation_path");
32
+ define_rb_intern!(static ID_CTX_KIND: "kind");
33
+
34
+ #[derive(TypedData)]
35
+ #[magnus(
36
+ class = "JSONSchema::ValidationErrorKind",
37
+ free_immediately,
38
+ size,
39
+ mark
40
+ )]
41
+ pub struct ValidationErrorKind {
42
+ name: String,
43
+ data: Opaque<Value>,
44
+ }
45
+
46
+ impl magnus::typed_data::DataTypeFunctions for ValidationErrorKind {
47
+ fn mark(&self, marker: &gc::Marker) {
48
+ marker.mark(self.data);
49
+ }
50
+ }
51
+
52
+ #[inline]
53
+ fn rb_hash1(ruby: &Ruby, k1: StaticSymbol, v1: Value) -> Result<Value, Error> {
54
+ let hash = ruby.hash_new_capa(1);
55
+ hash.aset(k1, v1)?;
56
+ Ok(hash.as_value())
57
+ }
58
+
59
+ #[inline]
60
+ fn rb_hash2(
61
+ ruby: &Ruby,
62
+ k1: StaticSymbol,
63
+ v1: Value,
64
+ k2: StaticSymbol,
65
+ v2: Value,
66
+ ) -> Result<Value, Error> {
67
+ let hash = ruby.hash_new_capa(2);
68
+ hash.aset(k1, v1)?;
69
+ hash.aset(k2, v2)?;
70
+ Ok(hash.as_value())
71
+ }
72
+
73
+ /// Convert anyOf/oneOf context into a Ruby array of error branch arrays.
74
+ fn context_to_ruby(
75
+ ruby: &Ruby,
76
+ context: &[Vec<jsonschema::ValidationError<'static>>],
77
+ mask: Option<&str>,
78
+ ) -> Result<Value, Error> {
79
+ let sym_message = ID_MESSAGE.to_symbol();
80
+ let sym_instance_path = ID_CTX_INSTANCE_PATH.to_symbol();
81
+ let sym_schema_path = ID_CTX_SCHEMA_PATH.to_symbol();
82
+ let sym_evaluation_path = ID_CTX_EVALUATION_PATH.to_symbol();
83
+ let sym_kind = ID_CTX_KIND.to_symbol();
84
+
85
+ let branches = ruby.ary_new_capa(context.len());
86
+ for branch in context {
87
+ let errors = ruby.ary_new_capa(branch.len());
88
+ for e in branch {
89
+ let hash = ruby.hash_new_capa(5);
90
+ let message = if let Some(mask) = mask {
91
+ e.masked_with(mask).to_string()
92
+ } else {
93
+ e.to_string()
94
+ };
95
+ hash.aset(sym_message, ruby.into_value(message.as_str()))?;
96
+ hash.aset(
97
+ sym_instance_path,
98
+ ruby.into_value(e.instance_path().as_str()),
99
+ )?;
100
+ hash.aset(sym_schema_path, ruby.into_value(e.schema_path().as_str()))?;
101
+ hash.aset(
102
+ sym_evaluation_path,
103
+ ruby.into_value(e.evaluation_path().as_str()),
104
+ )?;
105
+ hash.aset(sym_kind, ruby.into_value(e.kind().keyword()))?;
106
+ errors.push(hash)?;
107
+ }
108
+ branches.push(errors)?;
109
+ }
110
+ Ok(branches.as_value())
111
+ }
112
+
113
+ fn strings_to_ruby(ruby: &Ruby, strings: &[String]) -> Value {
114
+ ruby.ary_from_iter(strings.iter().map(|s| ruby.into_value(s.as_str())))
115
+ .as_value()
116
+ }
117
+
118
+ impl ValidationErrorKind {
119
+ pub fn new(
120
+ ruby: &Ruby,
121
+ kind: &jsonschema::error::ValidationErrorKind,
122
+ mask: Option<&str>,
123
+ ) -> Result<Self, Error> {
124
+ use jsonschema::error::ValidationErrorKind as K;
125
+
126
+ let name = kind.keyword();
127
+
128
+ let data = match kind {
129
+ K::AdditionalItems { limit } => rb_hash1(
130
+ ruby,
131
+ ID_LIMIT.to_symbol(),
132
+ ruby.integer_from_u64(*limit as u64).as_value(),
133
+ )?,
134
+ K::AdditionalProperties { unexpected }
135
+ | K::UnevaluatedItems { unexpected }
136
+ | K::UnevaluatedProperties { unexpected } => rb_hash1(
137
+ ruby,
138
+ ID_UNEXPECTED.to_symbol(),
139
+ strings_to_ruby(ruby, unexpected),
140
+ )?,
141
+ K::AnyOf { context } => rb_hash1(
142
+ ruby,
143
+ ID_CONTEXT.to_symbol(),
144
+ context_to_ruby(ruby, context, mask)?,
145
+ )?,
146
+ K::BacktrackLimitExceeded { error } => rb_hash1(
147
+ ruby,
148
+ ID_ERROR.to_symbol(),
149
+ ruby.into_value(error.to_string().as_str()),
150
+ )?,
151
+ K::Constant { expected_value } => rb_hash1(
152
+ ruby,
153
+ ID_EXPECTED_VALUE.to_symbol(),
154
+ value_to_ruby(ruby, expected_value)?,
155
+ )?,
156
+ K::Contains | K::FalseSchema | K::UniqueItems => ruby.hash_new().as_value(),
157
+ K::ContentEncoding { content_encoding } => rb_hash1(
158
+ ruby,
159
+ ID_CONTENT_ENCODING.to_symbol(),
160
+ ruby.into_value(content_encoding.as_str()),
161
+ )?,
162
+ K::ContentMediaType { content_media_type } => rb_hash1(
163
+ ruby,
164
+ ID_CONTENT_MEDIA_TYPE.to_symbol(),
165
+ ruby.into_value(content_media_type.as_str()),
166
+ )?,
167
+ K::Custom { message, .. } => rb_hash1(
168
+ ruby,
169
+ ID_MESSAGE.to_symbol(),
170
+ ruby.into_value(message.as_str()),
171
+ )?,
172
+ K::Enum { options } => {
173
+ rb_hash1(ruby, ID_OPTIONS.to_symbol(), value_to_ruby(ruby, options)?)?
174
+ }
175
+ K::ExclusiveMaximum { limit }
176
+ | K::ExclusiveMinimum { limit }
177
+ | K::Maximum { limit }
178
+ | K::Minimum { limit } => {
179
+ rb_hash1(ruby, ID_LIMIT.to_symbol(), value_to_ruby(ruby, limit)?)?
180
+ }
181
+ K::Format { format } => rb_hash1(
182
+ ruby,
183
+ ID_FORMAT.to_symbol(),
184
+ ruby.into_value(format.as_str()),
185
+ )?,
186
+ K::FromUtf8 { error } => rb_hash1(
187
+ ruby,
188
+ ID_ERROR.to_symbol(),
189
+ ruby.into_value(error.to_string().as_str()),
190
+ )?,
191
+ K::MaxItems { limit }
192
+ | K::MaxLength { limit }
193
+ | K::MaxProperties { limit }
194
+ | K::MinItems { limit }
195
+ | K::MinLength { limit }
196
+ | K::MinProperties { limit } => rb_hash1(
197
+ ruby,
198
+ ID_LIMIT.to_symbol(),
199
+ ruby.integer_from_u64(*limit).as_value(),
200
+ )?,
201
+ K::MultipleOf { multiple_of } => rb_hash1(
202
+ ruby,
203
+ ID_MULTIPLE_OF.to_symbol(),
204
+ value_to_ruby(ruby, multiple_of)?,
205
+ )?,
206
+ K::Not { schema } => {
207
+ rb_hash1(ruby, ID_SCHEMA.to_symbol(), value_to_ruby(ruby, schema)?)?
208
+ }
209
+ K::OneOfMultipleValid { context } => rb_hash2(
210
+ ruby,
211
+ ID_REASON.to_symbol(),
212
+ ruby.into_value("multipleValid"),
213
+ ID_CONTEXT.to_symbol(),
214
+ context_to_ruby(ruby, context, mask)?,
215
+ )?,
216
+ K::OneOfNotValid { context } => rb_hash2(
217
+ ruby,
218
+ ID_REASON.to_symbol(),
219
+ ruby.into_value("notValid"),
220
+ ID_CONTEXT.to_symbol(),
221
+ context_to_ruby(ruby, context, mask)?,
222
+ )?,
223
+ K::Pattern { pattern } => rb_hash1(
224
+ ruby,
225
+ ID_PATTERN.to_symbol(),
226
+ ruby.into_value(pattern.as_str()),
227
+ )?,
228
+ K::PropertyNames { error } => {
229
+ let message = if let Some(mask) = mask {
230
+ error.masked_with(mask).to_string()
231
+ } else {
232
+ error.to_string()
233
+ };
234
+ rb_hash1(
235
+ ruby,
236
+ ID_ERROR.to_symbol(),
237
+ ruby.into_value(message.as_str()),
238
+ )?
239
+ }
240
+ K::Referencing(err) => rb_hash1(
241
+ ruby,
242
+ ID_ERROR.to_symbol(),
243
+ ruby.into_value(err.to_string().as_str()),
244
+ )?,
245
+ K::Required { property } => rb_hash1(
246
+ ruby,
247
+ ID_PROPERTY.to_symbol(),
248
+ value_to_ruby(ruby, property)?,
249
+ )?,
250
+ K::Type { kind } => {
251
+ let types: Vec<Value> = match kind {
252
+ jsonschema::error::TypeKind::Single(ty) => vec![ruby.into_value(ty.as_str())],
253
+ jsonschema::error::TypeKind::Multiple(types) => types
254
+ .iter()
255
+ .map(|ty| ruby.into_value(ty.as_str()))
256
+ .collect(),
257
+ };
258
+ let rb_types = ruby.ary_from_iter(types);
259
+ rb_hash1(ruby, ID_TYPES.to_symbol(), rb_types.as_value())?
260
+ }
261
+ };
262
+
263
+ Ok(ValidationErrorKind {
264
+ name: name.to_string(),
265
+ data: data.into(),
266
+ })
267
+ }
268
+
269
+ fn name(&self) -> &str {
270
+ &self.name
271
+ }
272
+
273
+ fn value(ruby: &Ruby, rb_self: &Self) -> Value {
274
+ ruby.get_inner(rb_self.data)
275
+ }
276
+
277
+ fn as_hash(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
278
+ let hash = ruby.hash_new_capa(2);
279
+ hash.aset(ID_NAME.to_symbol(), rb_self.name.as_str())?;
280
+ hash.aset(ID_VALUE.to_symbol(), ruby.get_inner(rb_self.data))?;
281
+ Ok(hash.as_value())
282
+ }
283
+
284
+ fn inspect(&self) -> String {
285
+ format!("#<JSONSchema::ValidationErrorKind name={:?}>", self.name)
286
+ }
287
+
288
+ fn to_s(&self) -> &str {
289
+ &self.name
290
+ }
291
+ }
292
+
293
+ pub fn define_class(ruby: &Ruby, module: &RModule) -> Result<(), Error> {
294
+ let class = module.define_class("ValidationErrorKind", ruby.class_object())?;
295
+ class.define_method("name", method!(ValidationErrorKind::name, 0))?;
296
+ class.define_method("value", method!(ValidationErrorKind::value, 0))?;
297
+ class.define_method("to_h", method!(ValidationErrorKind::as_hash, 0))?;
298
+ class.define_method("inspect", method!(ValidationErrorKind::inspect, 0))?;
299
+ class.define_method("to_s", method!(ValidationErrorKind::to_s, 0))?;
300
+
301
+ Ok(())
302
+ }
data/src/evaluation.rs ADDED
@@ -0,0 +1,116 @@
1
+ //! Evaluation output wrapper for Ruby
2
+ //!
3
+ //! Provides full JSON Schema output format support: flag, list, and hierarchical.
4
+ use magnus::{method, prelude::*, Error, RModule, Ruby, Value};
5
+
6
+ use crate::{
7
+ ser::{serialize_to_ruby, value_to_ruby},
8
+ static_id::define_rb_intern,
9
+ };
10
+
11
+ define_rb_intern!(static ID_SCHEMA_LOCATION: "schemaLocation");
12
+ define_rb_intern!(static ID_ABSOLUTE_KEYWORD_LOCATION: "absoluteKeywordLocation");
13
+ define_rb_intern!(static ID_INSTANCE_LOCATION: "instanceLocation");
14
+ define_rb_intern!(static ID_ANNOTATIONS: "annotations");
15
+ define_rb_intern!(static ID_ERROR: "error");
16
+ define_rb_intern!(static ID_VALID: "valid");
17
+
18
+ #[magnus::wrap(class = "JSONSchema::Evaluation", free_immediately, size)]
19
+ pub struct Evaluation {
20
+ inner: jsonschema::Evaluation,
21
+ }
22
+
23
+ impl Evaluation {
24
+ pub fn new(output: jsonschema::Evaluation) -> Self {
25
+ Evaluation { inner: output }
26
+ }
27
+
28
+ fn is_valid(&self) -> bool {
29
+ self.inner.flag().valid
30
+ }
31
+
32
+ /// Simplest output format — only a "valid" key.
33
+ fn flag(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
34
+ let flag_output = rb_self.inner.flag();
35
+ let hash = ruby.hash_new_capa(1);
36
+ hash.aset(ID_VALID.to_symbol(), flag_output.valid)?;
37
+ Ok(hash.as_value())
38
+ }
39
+
40
+ /// Flat list of all evaluation results with annotations and errors.
41
+ fn list(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
42
+ let list_output = rb_self.inner.list();
43
+ serialize_to_ruby(ruby, &list_output)
44
+ }
45
+
46
+ /// Nested tree structure following the schema structure.
47
+ fn hierarchical(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
48
+ let hierarchical_output = rb_self.inner.hierarchical();
49
+ serialize_to_ruby(ruby, &hierarchical_output)
50
+ }
51
+
52
+ fn annotations(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
53
+ let schema_loc = ID_SCHEMA_LOCATION.to_symbol();
54
+ let abs_kw_loc = ID_ABSOLUTE_KEYWORD_LOCATION.to_symbol();
55
+ let inst_loc = ID_INSTANCE_LOCATION.to_symbol();
56
+ let annotations_key = ID_ANNOTATIONS.to_symbol();
57
+ let arr = ruby.ary_new();
58
+ for entry in rb_self.inner.iter_annotations() {
59
+ let hash = ruby.hash_new_capa(4);
60
+ hash.aset(schema_loc, entry.schema_location)?;
61
+ if let Some(uri) = entry.absolute_keyword_location {
62
+ hash.aset(abs_kw_loc, uri.as_str())?;
63
+ } else {
64
+ hash.aset(abs_kw_loc, ruby.qnil())?;
65
+ }
66
+ hash.aset(inst_loc, entry.instance_location.as_str())?;
67
+ hash.aset(
68
+ annotations_key,
69
+ value_to_ruby(ruby, entry.annotations.value())?,
70
+ )?;
71
+ arr.push(hash)?;
72
+ }
73
+ Ok(arr.as_value())
74
+ }
75
+
76
+ fn errors(ruby: &Ruby, rb_self: &Self) -> Result<Value, Error> {
77
+ let schema_loc = ID_SCHEMA_LOCATION.to_symbol();
78
+ let abs_kw_loc = ID_ABSOLUTE_KEYWORD_LOCATION.to_symbol();
79
+ let inst_loc = ID_INSTANCE_LOCATION.to_symbol();
80
+ let error_key = ID_ERROR.to_symbol();
81
+ let arr = ruby.ary_new();
82
+ for entry in rb_self.inner.iter_errors() {
83
+ let hash = ruby.hash_new_capa(4);
84
+ hash.aset(schema_loc, entry.schema_location)?;
85
+ if let Some(uri) = entry.absolute_keyword_location {
86
+ hash.aset(abs_kw_loc, uri.as_str())?;
87
+ } else {
88
+ hash.aset(abs_kw_loc, ruby.qnil())?;
89
+ }
90
+ hash.aset(inst_loc, entry.instance_location.as_str())?;
91
+ hash.aset(error_key, entry.error.to_string())?;
92
+ arr.push(hash)?;
93
+ }
94
+ Ok(arr.as_value())
95
+ }
96
+
97
+ fn inspect(&self) -> String {
98
+ format!(
99
+ "#<JSONSchema::Evaluation valid={}>",
100
+ self.inner.flag().valid
101
+ )
102
+ }
103
+ }
104
+
105
+ pub fn define_class(ruby: &Ruby, module: &RModule) -> Result<(), Error> {
106
+ let class = module.define_class("Evaluation", ruby.class_object())?;
107
+ class.define_method("valid?", method!(Evaluation::is_valid, 0))?;
108
+ class.define_method("flag", method!(Evaluation::flag, 0))?;
109
+ class.define_method("list", method!(Evaluation::list, 0))?;
110
+ class.define_method("hierarchical", method!(Evaluation::hierarchical, 0))?;
111
+ class.define_method("annotations", method!(Evaluation::annotations, 0))?;
112
+ class.define_method("errors", method!(Evaluation::errors, 0))?;
113
+ class.define_method("inspect", method!(Evaluation::inspect, 0))?;
114
+
115
+ Ok(())
116
+ }