Class: Pressroom::Blocks::Schema
- Inherits:
-
Object
- Object
- Pressroom::Blocks::Schema
- Defined in:
- lib/pressroom/blocks/schema.rb
Overview
Declarative description of one block's data payload.
The schema exists for security first and validation second: it records which fields carry HTML from Editor.js inline tools, so the renderer knows exactly what to run through the sanitizer and what to escape.
Defined Under Namespace
Classes: Field
Constant Summary collapse
- SCALAR_TYPES =
%i[string html integer boolean enum].freeze
- MAX_TREE_DEPTH =
Guards against a hand-crafted document nesting a list deep enough to exhaust the stack during coercion or rendering.
8
Instance Attribute Summary collapse
-
#fields ⇒ Object
readonly
Returns the value of attribute fields.
Class Method Summary collapse
Instance Method Summary collapse
- #array(name, of:, required: false, default: nil) ⇒ Object
- #boolean(name, required: false, default: false) ⇒ Object
-
#coerce(data) ⇒ Object
Returns [normalized_data, errors].
-
#enum(name, required: false, default: nil, **options) ⇒ Object
in:is a Ruby keyword and cannot be a parameter name, so it is collected through the options hash. - #html(name, required: false, default: nil) ⇒ Object
-
#html_fields ⇒ Object
Names of fields whose values must be sanitized before rendering.
-
#initialize ⇒ Schema
constructor
A new instance of Schema.
- #integer(name, required: false, default: nil) ⇒ Object
- #nested(name, required: false, &block) ⇒ Object
-
#sanitize(data, sanitizer) ⇒ Object
Applies the sanitizer to every field that can carry markup.
- #string(name, required: false, default: nil) ⇒ Object
-
#tree(name, content: :html, required: false) ⇒ Object
A recursive list, as produced by @editorjs/list 2.x: [{ "content" => "a", "items" => [{ "content" => "a1", "items" => [] }] }].
Constructor Details
#initialize ⇒ Schema
Returns a new instance of Schema.
28 29 30 |
# File 'lib/pressroom/blocks/schema.rb', line 28 def initialize @fields = {} end |
Instance Attribute Details
#fields ⇒ Object (readonly)
Returns the value of attribute fields.
26 27 28 |
# File 'lib/pressroom/blocks/schema.rb', line 26 def fields @fields end |
Class Method Details
.build(&block) ⇒ Object
20 21 22 23 24 |
# File 'lib/pressroom/blocks/schema.rb', line 20 def self.build(&block) schema = new schema.instance_eval(&block) if block schema end |
Instance Method Details
#array(name, of:, required: false, default: nil) ⇒ Object
55 56 57 |
# File 'lib/pressroom/blocks/schema.rb', line 55 def array(name, of:, required: false, default: nil) add(name, :array, required, default || [], { of: of.to_sym }) end |
#boolean(name, required: false, default: false) ⇒ Object
44 45 46 |
# File 'lib/pressroom/blocks/schema.rb', line 44 def boolean(name, required: false, default: false) add(name, :boolean, required, default) end |
#coerce(data) ⇒ Object
Returns [normalized_data, errors]. Never raises: a malformed payload degrades to defaults so that one bad article cannot take a page down.
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'lib/pressroom/blocks/schema.rb', line 113 def coerce(data) data = data.is_a?(Hash) ? stringify(data) : {} errors = [] result = {} fields.each do |name, field| if !data.key?(name) || data[name].nil? errors << "#{name} is required" if field.required result[name] = dup_default(field) next end value, error = cast(field, data[name]) errors << "#{name}: #{error}" if error result[name] = error ? dup_default(field) : value end [result, errors] end |
#enum(name, required: false, default: nil, **options) ⇒ Object
in: is a Ruby keyword and cannot be a parameter name, so it is
collected through the options hash.
50 51 52 53 |
# File 'lib/pressroom/blocks/schema.rb', line 50 def enum(name, required: false, default: nil, **) values = Array(.fetch(:in)).map(&:to_s) add(name, :enum, required, default, { values: values }) end |
#html(name, required: false, default: nil) ⇒ Object
36 37 38 |
# File 'lib/pressroom/blocks/schema.rb', line 36 def html(name, required: false, default: nil) add(name, :html, required, default) end |
#html_fields ⇒ Object
Names of fields whose values must be sanitized before rendering.
73 74 75 76 77 78 79 80 81 |
# File 'lib/pressroom/blocks/schema.rb', line 73 def html_fields fields.each_value.filter_map do |field| next field.name if field.type == :html next field.name if field.type == :array && field.[:of] == :html next field.name if field.type == :tree && field.[:content] == :html nil end end |
#integer(name, required: false, default: nil) ⇒ Object
40 41 42 |
# File 'lib/pressroom/blocks/schema.rb', line 40 def integer(name, required: false, default: nil) add(name, :integer, required, default) end |
#nested(name, required: false, &block) ⇒ Object
59 60 61 |
# File 'lib/pressroom/blocks/schema.rb', line 59 def nested(name, required: false, &block) add(name, :nested, required, nil, {}, Schema.build(&block)) end |
#sanitize(data, sanitizer) ⇒ Object
Applies the sanitizer to every field that can carry markup.
This lives on the schema rather than the renderer because only the schema knows the shape of each field: a flat string, an array, a nested object, or a recursive tree.
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/pressroom/blocks/schema.rb', line 88 def sanitize(data, sanitizer) result = data.dup fields.each do |name, field| case field.type when :html result[name] = sanitizer.sanitize(result[name]) when :array next unless field.[:of] == :html result[name] = Array(result[name]).map { |item| sanitizer.sanitize(item) } when :tree next unless field.[:content] == :html result[name] = sanitize_tree(Array(result[name]), sanitizer) when :nested result[name] = field.schema.sanitize(result[name] || {}, sanitizer) end end result end |
#string(name, required: false, default: nil) ⇒ Object
32 33 34 |
# File 'lib/pressroom/blocks/schema.rb', line 32 def string(name, required: false, default: nil) add(name, :string, required, default) end |
#tree(name, content: :html, required: false) ⇒ Object
A recursive list, as produced by @editorjs/list 2.x:
[{ "content" => "a", "items" => [{ "content" => "a1", "items" => [] }] }]
Plain strings are accepted too, so documents written by the 1.x tool keep rendering after an upgrade.
68 69 70 |
# File 'lib/pressroom/blocks/schema.rb', line 68 def tree(name, content: :html, required: false) add(name, :tree, required, [], { content: content.to_sym }) end |