easol-canvas 4.20.0 → 4.21.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c37703170f2a9f0870de51fa994e8929d1ed3a53ebe7f510c27ab943bddf9b6c
|
|
4
|
+
data.tar.gz: 1ccfe78c1b4aaaf9cb4bcbbd1d0afc062b63e94d93b41e162174aedff5fe9832
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f2ca1fc7300356060311c3e1622b05df28e9d9e48985a8d50a40319c83d15a882a4ac7174c9f39bdc501f47e2988e8a9e9980187dbf513d03b6c8193f927f62
|
|
7
|
+
data.tar.gz: 883efd325fda12742d09a6ae3b30c102c51406fec1caf10f112d1ee8ce6ea9f145de80e9c1a4d6dd2f8562c7b5909aee99895a72207108c610d735a89a318db6
|
|
@@ -25,6 +25,7 @@ module Canvas
|
|
|
25
25
|
#
|
|
26
26
|
class CustomType
|
|
27
27
|
REQUIRED_KEYS = %w[key name attributes].freeze
|
|
28
|
+
OPTIONAL_KEYS = %w[layout].freeze
|
|
28
29
|
|
|
29
30
|
attr_reader :schema, :errors, :custom_types
|
|
30
31
|
|
|
@@ -43,7 +44,8 @@ module Canvas
|
|
|
43
44
|
ensure_key_value_is_not_reserved &&
|
|
44
45
|
ensure_no_duplicate_attributes &&
|
|
45
46
|
ensure_attributes_are_valid &&
|
|
46
|
-
ensure_first_attribute_not_array
|
|
47
|
+
ensure_first_attribute_not_array &&
|
|
48
|
+
ensure_layout_is_valid
|
|
47
49
|
|
|
48
50
|
errors.empty?
|
|
49
51
|
end
|
|
@@ -66,7 +68,7 @@ module Canvas
|
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
def ensure_no_unrecognized_keys
|
|
69
|
-
unrecognized_keys = schema.keys - REQUIRED_KEYS
|
|
71
|
+
unrecognized_keys = schema.keys - REQUIRED_KEYS - OPTIONAL_KEYS
|
|
70
72
|
return true if unrecognized_keys.empty?
|
|
71
73
|
|
|
72
74
|
@errors << "Unrecognized keys: #{unrecognized_keys.join(', ')}"
|
|
@@ -145,6 +147,16 @@ module Canvas
|
|
|
145
147
|
@errors << "The first attribute cannot be an array"
|
|
146
148
|
false
|
|
147
149
|
end
|
|
150
|
+
|
|
151
|
+
def ensure_layout_is_valid
|
|
152
|
+
return true unless schema["layout"]
|
|
153
|
+
|
|
154
|
+
layout_validator = CustomTypeLayoutSchema.new(schema:)
|
|
155
|
+
return true if layout_validator.validate
|
|
156
|
+
|
|
157
|
+
@errors += layout_validator.errors
|
|
158
|
+
false
|
|
159
|
+
end
|
|
148
160
|
end
|
|
149
161
|
end
|
|
150
162
|
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "json-schema"
|
|
5
|
+
|
|
6
|
+
module Canvas
|
|
7
|
+
module Validator
|
|
8
|
+
# :documented:
|
|
9
|
+
# This class is used to validate a layout definition for a custom type.
|
|
10
|
+
# Custom type layouts use a flat array of elements (unlike block layouts which use tabs).
|
|
11
|
+
#
|
|
12
|
+
# Example of a valid custom type layout definition:
|
|
13
|
+
# {
|
|
14
|
+
# "attributes" => [
|
|
15
|
+
# { "name" => "question", "type" => "string" },
|
|
16
|
+
# { "name" => "answer", "type" => "text" },
|
|
17
|
+
# { "name" => "show_icon", "type" => "boolean" }
|
|
18
|
+
# ],
|
|
19
|
+
# "layout" => [
|
|
20
|
+
# "question",
|
|
21
|
+
# {
|
|
22
|
+
# "type" => "accordion",
|
|
23
|
+
# "label" => "Advanced",
|
|
24
|
+
# "elements" => [
|
|
25
|
+
# "answer",
|
|
26
|
+
# { "type" => "attribute", "name" => "show_icon" }
|
|
27
|
+
# ]
|
|
28
|
+
# }
|
|
29
|
+
# ]
|
|
30
|
+
# }
|
|
31
|
+
class CustomTypeLayoutSchema
|
|
32
|
+
attr_reader :errors
|
|
33
|
+
|
|
34
|
+
def initialize(schema:)
|
|
35
|
+
@schema = schema
|
|
36
|
+
@errors = []
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def validate
|
|
40
|
+
@errors = []
|
|
41
|
+
|
|
42
|
+
if ensure_valid_format
|
|
43
|
+
ensure_no_unrecognized_keys
|
|
44
|
+
ensure_no_duplicate_keys
|
|
45
|
+
ensure_accordion_toggles_are_valid
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
@errors.empty?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
attr_reader :schema
|
|
54
|
+
|
|
55
|
+
def ensure_no_duplicate_keys
|
|
56
|
+
attributes = fetch_all_attribute_names
|
|
57
|
+
duplicates =
|
|
58
|
+
attributes
|
|
59
|
+
.group_by { |(key)| key }
|
|
60
|
+
.filter { |_key, usage| usage.size > 1 }
|
|
61
|
+
|
|
62
|
+
return if duplicates.empty?
|
|
63
|
+
|
|
64
|
+
duplicates.each do |attribute, usage|
|
|
65
|
+
@errors << "Duplicated attribute key `#{attribute}` found. "\
|
|
66
|
+
"Location: #{usage.map { |(_, location)| location }.join(', ')}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def ensure_no_unrecognized_keys
|
|
71
|
+
attributes = fetch_all_attribute_names
|
|
72
|
+
defined_attributes = schema_attributes.map { |attr| normalize_attribute(attr["name"]) }
|
|
73
|
+
|
|
74
|
+
attributes.each do |attribute, location|
|
|
75
|
+
unless defined_attributes.include?(attribute)
|
|
76
|
+
@errors << "Unrecognized attribute `#{attribute}`. Location: #{location}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @return [Array<Array(String, String)>] an array of all the attribute names that
|
|
82
|
+
# are mentioned in the layout schema, along with its path. The names are
|
|
83
|
+
# normalized, i.e. downcased.
|
|
84
|
+
def fetch_all_attribute_names
|
|
85
|
+
attributes = fetch_elements_of_type("attribute")
|
|
86
|
+
attributes.map do |(node, path)|
|
|
87
|
+
[
|
|
88
|
+
normalize_attribute(node.is_a?(Hash) ? node["name"] : node),
|
|
89
|
+
path
|
|
90
|
+
]
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @param type [String] the element type to fetch
|
|
95
|
+
# @return [Array<Array(<Hash, String>, String)] an array of elements that match
|
|
96
|
+
# the given type. Each element is an array containing the node and its path.
|
|
97
|
+
def fetch_elements_of_type(type)
|
|
98
|
+
elements = []
|
|
99
|
+
|
|
100
|
+
fetch_element = lambda { |node, path|
|
|
101
|
+
if type == "attribute" && node.is_a?(String)
|
|
102
|
+
elements << [node, path]
|
|
103
|
+
elsif node.is_a?(Hash) && node["type"] == type
|
|
104
|
+
elements << [node, path]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if node.is_a?(Hash) && node.key?("elements")
|
|
108
|
+
node["elements"].each_with_index do |element, i|
|
|
109
|
+
current_path = "#{path}/elements/#{i}"
|
|
110
|
+
fetch_element.call(element, current_path)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Flat layout: iterate directly over layout elements
|
|
116
|
+
layout_schema.each_with_index do |element, i|
|
|
117
|
+
current_path = "layout/#{i}"
|
|
118
|
+
fetch_element.call(element, current_path)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
elements
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def ensure_valid_format
|
|
125
|
+
result = JSON::Validator.fully_validate(
|
|
126
|
+
schema_definition,
|
|
127
|
+
{ "layout" => layout_schema },
|
|
128
|
+
strict: true,
|
|
129
|
+
clear_cache: true
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return true if result.empty?
|
|
133
|
+
|
|
134
|
+
@errors += result
|
|
135
|
+
false
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def ensure_accordion_toggles_are_valid
|
|
139
|
+
accordion_toggles = fetch_elements_of_type("accordion_toggle")
|
|
140
|
+
accordion_toggles.each do |accordion_toggle, location|
|
|
141
|
+
toggle_attribute = schema_attributes.detect { |attr|
|
|
142
|
+
attr["name"] == accordion_toggle["toggle_attribute"]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if toggle_attribute.nil?
|
|
146
|
+
@errors << "The toggle_attribute in accordion_toggle is unrecognized. Location: #{location}"
|
|
147
|
+
elsif toggle_attribute["type"]&.downcase != "boolean"
|
|
148
|
+
@errors << "The toggle_attribute in accordion_toggle must be a boolean. Location: #{location}"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def layout_schema
|
|
154
|
+
schema["layout"] || []
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def schema_definition
|
|
158
|
+
File.read(
|
|
159
|
+
File.join(File.dirname(__FILE__), "../../../", "schema_definitions", "custom_type_layout.json")
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def normalize_attribute(name)
|
|
164
|
+
name.to_s.strip.downcase
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def schema_attributes
|
|
168
|
+
schema["attributes"] || []
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
data/lib/canvas/version.rb
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"type": "object",
|
|
3
|
+
"properties": {
|
|
4
|
+
"layout": {
|
|
5
|
+
"type": "array",
|
|
6
|
+
"items": {
|
|
7
|
+
"oneOf": [
|
|
8
|
+
{ "type": "string" },
|
|
9
|
+
{ "$ref": "#/$defs/accordion" },
|
|
10
|
+
{ "$ref": "#/$defs/accordion_toggle" },
|
|
11
|
+
{ "$ref": "#/$defs/attribute" }
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"$defs": {
|
|
17
|
+
"accordion": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"required": ["label", "type"],
|
|
20
|
+
"properties": {
|
|
21
|
+
"type": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"const": "accordion"
|
|
24
|
+
},
|
|
25
|
+
"label": {
|
|
26
|
+
"type": "string"
|
|
27
|
+
},
|
|
28
|
+
"elements": {
|
|
29
|
+
"type": "array",
|
|
30
|
+
"items": {
|
|
31
|
+
"oneOf": [
|
|
32
|
+
{ "type": "string" },
|
|
33
|
+
{ "$ref": "#/$defs/accordion_toggle" },
|
|
34
|
+
{ "$ref": "#/$defs/attribute" }
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"additionalProperties": false
|
|
40
|
+
},
|
|
41
|
+
"accordion_toggle": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"required": ["type", "toggle_attribute", "elements"],
|
|
44
|
+
"properties": {
|
|
45
|
+
"type": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"const": "accordion_toggle"
|
|
48
|
+
},
|
|
49
|
+
"toggle_attribute": {
|
|
50
|
+
"type": "string"
|
|
51
|
+
},
|
|
52
|
+
"elements": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"items": {
|
|
55
|
+
"oneOf": [
|
|
56
|
+
{ "type": "string" },
|
|
57
|
+
{ "$ref": "#/$defs/attribute" }
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"additionalProperties": false
|
|
63
|
+
},
|
|
64
|
+
"attribute": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"required": ["name", "type"],
|
|
67
|
+
"properties": {
|
|
68
|
+
"type": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"const": "attribute"
|
|
71
|
+
},
|
|
72
|
+
"name": {
|
|
73
|
+
"type": "string"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"additionalProperties": false
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: easol-canvas
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.21.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kyle Byrne
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2026-01-
|
|
12
|
+
date: 2026-01-27 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: thor
|
|
@@ -129,6 +129,7 @@ files:
|
|
|
129
129
|
- lib/canvas/services/front_matter_extractor.rb
|
|
130
130
|
- lib/canvas/validators/block_schema.rb
|
|
131
131
|
- lib/canvas/validators/custom_type.rb
|
|
132
|
+
- lib/canvas/validators/custom_type_layout_schema.rb
|
|
132
133
|
- lib/canvas/validators/footer_schema.rb
|
|
133
134
|
- lib/canvas/validators/html.rb
|
|
134
135
|
- lib/canvas/validators/json.rb
|
|
@@ -157,6 +158,7 @@ files:
|
|
|
157
158
|
- lib/canvas/validators/schema_attributes/variant.rb
|
|
158
159
|
- lib/canvas/version.rb
|
|
159
160
|
- lib/easol/canvas.rb
|
|
161
|
+
- schema_definitions/custom_type_layout.json
|
|
160
162
|
- schema_definitions/layout.json
|
|
161
163
|
homepage: https://rubygems.org/gems/easol-canvas
|
|
162
164
|
licenses:
|