lutaml-model 0.3.9 → 0.3.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +39 -21
- data/README.adoc +263 -6
- data/lib/lutaml/model/attribute.rb +114 -3
- data/lib/lutaml/model/error/collection_count_out_of_range_error.rb +29 -0
- data/lib/lutaml/model/error/validation_error.rb +21 -0
- data/lib/lutaml/model/error.rb +2 -0
- data/lib/lutaml/model/schema_location.rb +59 -0
- data/lib/lutaml/model/serialize.rb +49 -33
- data/lib/lutaml/model/validation.rb +24 -0
- data/lib/lutaml/model/version.rb +1 -1
- data/lib/lutaml/model/xml_adapter/xml_document.rb +17 -7
- data/lutaml-model.gemspec +1 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5140f93f22f4b05222edc408aeca50552082e447b71fddd724b34ba9e6b82d55
|
4
|
+
data.tar.gz: 0aa44a3c72b65cca0ed2b7411614d8cdb0c44b4e8293e183935cb45ba7818d7c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69e4d0d0a3a5bd19e11a43fd33e03566ca035d18cf948a0a2fc5abe5f880489e4a6ad347d8118a04c35c8c95be5b7eb29ca93d426643c7790ab045db2b34721c
|
7
|
+
data.tar.gz: df1699ba6647f53f97735d0986b92bf8f76d6bf44430117406100b257f5934ac4a7baa6a4243bb8384db277ecb975bf64efe2f1a16b293739b245150e800cb15
|
data/.rubocop_todo.yml
CHANGED
@@ -1,27 +1,26 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on 2024-09-
|
3
|
+
# on 2024-09-10 23:53:08 UTC using RuboCop version 1.66.1.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
8
8
|
|
9
|
-
# Offense count:
|
10
|
-
# This cop supports safe autocorrection (--autocorrect).
|
11
|
-
# Configuration parameters: Severity, Include.
|
12
|
-
# Include: **/*.gemspec
|
13
|
-
Gemspec/RequireMFA:
|
14
|
-
Exclude:
|
15
|
-
- 'lutaml-model.gemspec'
|
16
|
-
|
17
|
-
# Offense count: 62
|
9
|
+
# Offense count: 88
|
18
10
|
# This cop supports safe autocorrection (--autocorrect).
|
19
11
|
# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
|
20
12
|
# URISchemes: http, https
|
21
13
|
Layout/LineLength:
|
22
14
|
Enabled: false
|
23
15
|
|
24
|
-
# Offense count:
|
16
|
+
# Offense count: 1
|
17
|
+
# This cop supports safe autocorrection (--autocorrect).
|
18
|
+
# Configuration parameters: AllowInHeredoc.
|
19
|
+
Layout/TrailingWhitespace:
|
20
|
+
Exclude:
|
21
|
+
- 'lib/lutaml/model/schema_location.rb'
|
22
|
+
|
23
|
+
# Offense count: 11
|
25
24
|
# Configuration parameters: AllowedMethods.
|
26
25
|
# AllowedMethods: enums
|
27
26
|
Lint/ConstantDefinitionInBlock:
|
@@ -30,12 +29,19 @@ Lint/ConstantDefinitionInBlock:
|
|
30
29
|
- 'spec/lutaml/model/schema/relaxng_schema_spec.rb'
|
31
30
|
- 'spec/lutaml/model/schema/xsd_schema_spec.rb'
|
32
31
|
- 'spec/lutaml/model/schema/yaml_schema_spec.rb'
|
32
|
+
- 'spec/lutaml/model/validation_spec.rb'
|
33
33
|
- 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
|
34
34
|
|
35
|
-
# Offense count:
|
35
|
+
# Offense count: 1
|
36
|
+
Lint/DuplicateMethods:
|
37
|
+
Exclude:
|
38
|
+
- 'lib/lutaml/model/attribute.rb'
|
39
|
+
|
40
|
+
# Offense count: 31
|
36
41
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
|
37
42
|
Metrics/AbcSize:
|
38
43
|
Exclude:
|
44
|
+
- 'lib/lutaml/model/attribute.rb'
|
39
45
|
- 'lib/lutaml/model/comparable_model.rb'
|
40
46
|
- 'lib/lutaml/model/schema/relaxng_schema.rb'
|
41
47
|
- 'lib/lutaml/model/schema/xsd_schema.rb'
|
@@ -51,7 +57,7 @@ Metrics/AbcSize:
|
|
51
57
|
Metrics/BlockLength:
|
52
58
|
Max: 42
|
53
59
|
|
54
|
-
# Offense count:
|
60
|
+
# Offense count: 25
|
55
61
|
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
56
62
|
Metrics/CyclomaticComplexity:
|
57
63
|
Exclude:
|
@@ -63,17 +69,17 @@ Metrics/CyclomaticComplexity:
|
|
63
69
|
- 'lib/lutaml/model/xml_adapter/ox_adapter.rb'
|
64
70
|
- 'lib/lutaml/model/xml_adapter/xml_document.rb'
|
65
71
|
|
66
|
-
# Offense count:
|
72
|
+
# Offense count: 39
|
67
73
|
# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
|
68
74
|
Metrics/MethodLength:
|
69
|
-
Max:
|
75
|
+
Max: 49
|
70
76
|
|
71
77
|
# Offense count: 4
|
72
78
|
# Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
|
73
79
|
Metrics/ParameterLists:
|
74
80
|
Max: 9
|
75
81
|
|
76
|
-
# Offense count:
|
82
|
+
# Offense count: 21
|
77
83
|
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
|
78
84
|
Metrics/PerceivedComplexity:
|
79
85
|
Exclude:
|
@@ -94,7 +100,7 @@ RSpec/ContextWording:
|
|
94
100
|
- 'spec/lutaml/model/xml_adapter/ox_adapter_spec.rb'
|
95
101
|
- 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
|
96
102
|
|
97
|
-
# Offense count:
|
103
|
+
# Offense count: 101
|
98
104
|
# Configuration parameters: CountAsOne.
|
99
105
|
RSpec/ExampleLength:
|
100
106
|
Max: 57
|
@@ -105,13 +111,14 @@ RSpec/IndexedLet:
|
|
105
111
|
Exclude:
|
106
112
|
- 'spec/address_spec.rb'
|
107
113
|
|
108
|
-
# Offense count:
|
114
|
+
# Offense count: 19
|
109
115
|
RSpec/LeakyConstantDeclaration:
|
110
116
|
Exclude:
|
111
117
|
- 'spec/lutaml/model/schema/json_schema_spec.rb'
|
112
118
|
- 'spec/lutaml/model/schema/relaxng_schema_spec.rb'
|
113
119
|
- 'spec/lutaml/model/schema/xsd_schema_spec.rb'
|
114
120
|
- 'spec/lutaml/model/schema/yaml_schema_spec.rb'
|
121
|
+
- 'spec/lutaml/model/validation_spec.rb'
|
115
122
|
- 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
|
116
123
|
|
117
124
|
# Offense count: 4
|
@@ -122,23 +129,29 @@ RSpec/MultipleDescribes:
|
|
122
129
|
- 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
|
123
130
|
- 'spec/lutaml/model/xml_adapter_spec.rb'
|
124
131
|
|
125
|
-
# Offense count:
|
132
|
+
# Offense count: 88
|
126
133
|
RSpec/MultipleExpectations:
|
127
134
|
Max: 11
|
128
135
|
|
129
|
-
# Offense count:
|
136
|
+
# Offense count: 17
|
130
137
|
# Configuration parameters: AllowSubject.
|
131
138
|
RSpec/MultipleMemoizedHelpers:
|
132
139
|
Max: 9
|
133
140
|
|
134
|
-
# Offense count:
|
141
|
+
# Offense count: 7
|
135
142
|
RSpec/PendingWithoutReason:
|
136
143
|
Exclude:
|
137
144
|
- 'spec/lutaml/model/mixed_content_spec.rb'
|
145
|
+
- 'spec/lutaml/model/validation_spec.rb'
|
138
146
|
- 'spec/lutaml/model/xml_adapter/oga_adapter_spec.rb'
|
139
147
|
- 'spec/lutaml/model/xml_adapter/xml_namespace_spec.rb'
|
140
148
|
- 'spec/lutaml/model/xml_adapter_spec.rb'
|
141
149
|
|
150
|
+
# Offense count: 2
|
151
|
+
RSpec/RepeatedExampleGroupDescription:
|
152
|
+
Exclude:
|
153
|
+
- 'spec/lutaml/model/collection_spec.rb'
|
154
|
+
|
142
155
|
# Offense count: 1
|
143
156
|
# Configuration parameters: Include, CustomTransform, IgnoreMethods, IgnoreMetadata.
|
144
157
|
# Include: **/*_spec.rb
|
@@ -152,6 +165,11 @@ Security/CompoundHash:
|
|
152
165
|
Exclude:
|
153
166
|
- 'lib/lutaml/model/comparable_model.rb'
|
154
167
|
|
168
|
+
# Offense count: 1
|
169
|
+
Style/MissingRespondToMissing:
|
170
|
+
Exclude:
|
171
|
+
- 'lib/lutaml/model/serialize.rb'
|
172
|
+
|
155
173
|
# Offense count: 1
|
156
174
|
# Configuration parameters: AllowedMethods.
|
157
175
|
# AllowedMethods: respond_to_missing?
|
data/README.adoc
CHANGED
@@ -204,11 +204,45 @@ end
|
|
204
204
|
Define attributes as collections (arrays or hashes) to store multiple values
|
205
205
|
using the `collection` option.
|
206
206
|
|
207
|
+
`collection` can be set to:
|
208
|
+
|
209
|
+
`true`:::
|
210
|
+
The attribute contains an unbounded collection of objects of the declared class.
|
211
|
+
|
212
|
+
`{min}..{max}`:::
|
213
|
+
The attribute contains a collection of objects of the declared class with a
|
214
|
+
count within the specified range.
|
215
|
+
If the number of objects is out of this numbered range,
|
216
|
+
`CollectionCountOutOfRangeError` will be raised.
|
217
|
+
+
|
218
|
+
[example]
|
219
|
+
====
|
220
|
+
When set to `0..1`, it means that the attribute is optional, it could be empty
|
221
|
+
or contain one object of the declared class.
|
222
|
+
====
|
223
|
+
+
|
224
|
+
[example]
|
225
|
+
====
|
226
|
+
When set to `1..` (equivalent to `1..Infinity`), it means that the
|
227
|
+
attribute must contain at least one object of the declared class and can contain
|
228
|
+
any number of objects.
|
229
|
+
====
|
230
|
+
+
|
231
|
+
[example]
|
232
|
+
====
|
233
|
+
When set to 5..10` means that there is a minimum of 5 and a maximum of 10
|
234
|
+
objects of the declared class. If the count of values for the attribute is less
|
235
|
+
then 5 or greater then 10, the `CollectionCountOutOfRangeError` will be raised.
|
236
|
+
====
|
237
|
+
|
238
|
+
|
207
239
|
Syntax:
|
208
240
|
|
209
241
|
[source,ruby]
|
210
242
|
----
|
211
243
|
attribute :name_of_attribute, Type, collection: true
|
244
|
+
attribute :name_of_attribute, Type, collection: {min}..{max}
|
245
|
+
attribute :name_of_attribute, Type, collection: {min}..
|
212
246
|
----
|
213
247
|
|
214
248
|
.Using the `collection` option to define a collection attribute
|
@@ -219,18 +253,27 @@ attribute :name_of_attribute, Type, collection: true
|
|
219
253
|
class Studio < Lutaml::Model::Serializable
|
220
254
|
attribute :location, :string
|
221
255
|
attribute :potters, :string, collection: true
|
256
|
+
attribute :address, :string, collection: 1..2
|
257
|
+
attribute :hobbies, :string, collection: 0..
|
222
258
|
end
|
223
259
|
----
|
224
260
|
|
225
261
|
[source,ruby]
|
226
262
|
----
|
227
|
-
> Studio.new
|
263
|
+
> Studio.new
|
264
|
+
> # address count is `0`, must be between 1 and 2 (Lutaml::Model::CollectionCountOutOfRangeError)
|
265
|
+
> Studio.new({ address: ["address 1", "address 2", "address 3"] })
|
266
|
+
> # address count is `3`, must be between 1 and 2 (Lutaml::Model::CollectionCountOutOfRangeError)
|
267
|
+
> Studio.new({ address: ["address 1"] }).potters
|
228
268
|
> # []
|
229
|
-
> Studio.new(
|
269
|
+
> Studio.new({ address: ["address 1"] }).address
|
270
|
+
> # ["address 1"]
|
271
|
+
> Studio.new(address: ["address 1"], potters: ['John Doe', 'Jane Doe']).potters
|
230
272
|
> # ['John Doe', 'Jane Doe']
|
231
273
|
----
|
232
274
|
====
|
233
275
|
|
276
|
+
|
234
277
|
[[attribute-enumeration]]
|
235
278
|
=== Attribute as an enumeration
|
236
279
|
|
@@ -947,6 +990,125 @@ end
|
|
947
990
|
TODO: How to create mixed content from `#new`?
|
948
991
|
|
949
992
|
|
993
|
+
==== Automatic support of `xsi:schemaLocation`
|
994
|
+
|
995
|
+
The
|
996
|
+
https://www.w3.org/TR/xmlschema-1/#xsi_schemaLocation[W3C "XMLSchema-instance"]
|
997
|
+
namespace describes a number of attributes that can be used to control the
|
998
|
+
behavior of XML processors. One of these attributes is `xsi:schemaLocation`.
|
999
|
+
|
1000
|
+
The `xsi:schemaLocation` attribute locates schemas for elements and attributes
|
1001
|
+
that are in a specified namespace. Its value consists of pairs of a namespace
|
1002
|
+
URI followed by a relative or absolute URL where the schema for that namespace
|
1003
|
+
can be found.
|
1004
|
+
|
1005
|
+
Usage of `xsi:schemaLocation` in an XML element depends on the declaration of
|
1006
|
+
the XML namespace of `xsi`, i.e.
|
1007
|
+
`xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`. Without this namespace
|
1008
|
+
LutaML will not be able to serialize the `xsi:schemaLocation` attribute.
|
1009
|
+
|
1010
|
+
NOTE: It is most commonly attached to the root element but can appear further
|
1011
|
+
down the tree.
|
1012
|
+
|
1013
|
+
The following snippet shows how `xsi:schemaLocation` is used in an XML document:
|
1014
|
+
|
1015
|
+
[source,xml]
|
1016
|
+
----
|
1017
|
+
<cera:Ceramic
|
1018
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
1019
|
+
xmlns:cera="http://example.com/ceramic"
|
1020
|
+
xmlns:clr='http://example.com/color'
|
1021
|
+
xsi:schemaLocation=
|
1022
|
+
"http://example.com/ceramic http://example.com/ceramic.xsd
|
1023
|
+
http://example.com/color http://example.com/color.xsd"
|
1024
|
+
clr:color="navy-blue">
|
1025
|
+
<cera:Type>Porcelain</cera:Type>
|
1026
|
+
<Glaze>Clear</Glaze>
|
1027
|
+
</cera:Ceramic>
|
1028
|
+
----
|
1029
|
+
|
1030
|
+
LutaML::Model supports the `xsi:schemaLocation` attribute in all XML
|
1031
|
+
serializations by default, through the `schema_location` attribute on the model
|
1032
|
+
instance object.
|
1033
|
+
|
1034
|
+
.Retrieving and setting the `xsi:schemaLocation` attribute in XML serialization
|
1035
|
+
[example]
|
1036
|
+
====
|
1037
|
+
In this example, the `xsi:schemaLocation` attribute will be automatically
|
1038
|
+
supplied without the explicit need to define in the model, and allows for
|
1039
|
+
round-trip serialization.
|
1040
|
+
|
1041
|
+
[source,ruby]
|
1042
|
+
----
|
1043
|
+
class Ceramic < Lutaml::Model::Serializable
|
1044
|
+
attribute :type, :string
|
1045
|
+
attribute :glaze, :string
|
1046
|
+
attribute :color, :string
|
1047
|
+
|
1048
|
+
xml do
|
1049
|
+
root 'Ceramic'
|
1050
|
+
namespace 'http://example.com/ceramic', 'cera'
|
1051
|
+
map_element 'Type', to: :type, namespace: :inherit
|
1052
|
+
map_element 'Glaze', to: :glaze
|
1053
|
+
map_attribute 'color', to: :color, namespace: 'http://example.com/color', prefix: 'clr'
|
1054
|
+
end
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
xml_content = <<~HERE
|
1058
|
+
<cera:Ceramic
|
1059
|
+
xmlns:cera="http://example.com/ceramic"
|
1060
|
+
xmlns:clr="http://example.com/color"
|
1061
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
1062
|
+
clr:color="navy-blue"
|
1063
|
+
xsi:schemaLocation="
|
1064
|
+
http://example.com/ceramic http://example.com/ceramic.xsd
|
1065
|
+
http://example.com/color http://example.com/color.xsd
|
1066
|
+
">
|
1067
|
+
<cera:Type>Porcelain</cera:Type>
|
1068
|
+
<Glaze>Clear</Glaze>
|
1069
|
+
</cera:Ceramic>
|
1070
|
+
HERE
|
1071
|
+
----
|
1072
|
+
|
1073
|
+
[source,ruby]
|
1074
|
+
----
|
1075
|
+
> c = Ceramic.from_xml(xml_content)
|
1076
|
+
=>
|
1077
|
+
#<Ceramic:0x00000001222bdd60
|
1078
|
+
...
|
1079
|
+
> schema_loc = c.schema_location
|
1080
|
+
#<Lutaml::Model::SchemaLocation:0x0000000122773760
|
1081
|
+
...
|
1082
|
+
> schema_loc
|
1083
|
+
=>
|
1084
|
+
#<Lutaml::Model::SchemaLocation:0x0000000122773760
|
1085
|
+
@namespace="http://www.w3.org/2001/XMLSchema-instance",
|
1086
|
+
@original_schema_location="http://example.com/ceramic http://example.com/ceramic.xsd http://example.com/color http://example.com/color.xsd",
|
1087
|
+
@prefix="xsi",
|
1088
|
+
@schema_location=
|
1089
|
+
[#<Lutaml::Model::Location:0x00000001222bd018 @location="http://example.com/ceramic.xsd", @namespace="http://example.com/ceramic">,
|
1090
|
+
#<Lutaml::Model::Location:0x00000001222bcfc8 @location="http://example.com/color.xsd", @namespace="http://example.com/color">]>
|
1091
|
+
> new_c = Ceramic.new(type: "Porcelain", glaze: "Clear", color: "navy-blue", schema_location: schema_loc).to_xml
|
1092
|
+
> puts new_c
|
1093
|
+
# <cera:Ceramic
|
1094
|
+
# xmlns:cera="http://example.com/ceramic"
|
1095
|
+
# xmlns:clr="http://example.com/color"
|
1096
|
+
# xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
1097
|
+
# clr:color="navy-blue"
|
1098
|
+
# xsi:schemaLocation="
|
1099
|
+
# http://example.com/ceramic http://example.com/ceramic.xsd
|
1100
|
+
# http://example.com/color http://example.com/color.xsd
|
1101
|
+
# ">
|
1102
|
+
# <cera:Type>Porcelain</cera:Type>
|
1103
|
+
# <cera:Glaze>Clear</cera:Glaze>
|
1104
|
+
# </cera:Ceramic>
|
1105
|
+
----
|
1106
|
+
====
|
1107
|
+
|
1108
|
+
NOTE: For details on `xsi:schemaLocation`, please refer to the
|
1109
|
+
https://www.w3.org/TR/xmlschema-1/#xsi_schemaLocation[W3C XML standard].
|
1110
|
+
|
1111
|
+
|
950
1112
|
|
951
1113
|
=== Key value data models
|
952
1114
|
|
@@ -1333,12 +1495,12 @@ class CustomCeramic < Lutaml::Model::Serializable
|
|
1333
1495
|
map 'size', to: :size
|
1334
1496
|
end
|
1335
1497
|
|
1336
|
-
def name_to_json(model,
|
1337
|
-
doc["name"] = "Masterpiece: #{
|
1498
|
+
def name_to_json(model, doc)
|
1499
|
+
doc["name"] = "Masterpiece: #{model.name}"
|
1338
1500
|
end
|
1339
1501
|
|
1340
|
-
def name_from_json(model,
|
1341
|
-
model.name = value.sub(/^
|
1502
|
+
def name_from_json(model, value)
|
1503
|
+
model.name = value.sub(/^Masterpiece: /, '')
|
1342
1504
|
end
|
1343
1505
|
end
|
1344
1506
|
----
|
@@ -1547,6 +1709,101 @@ In this example:
|
|
1547
1709
|
====
|
1548
1710
|
|
1549
1711
|
|
1712
|
+
== Validation
|
1713
|
+
|
1714
|
+
=== General
|
1715
|
+
|
1716
|
+
Lutaml::Model provides a way to validate data models using the `validate` and
|
1717
|
+
`validate!` methods.
|
1718
|
+
|
1719
|
+
* The `validate` method sets an `errors` array in the model instance that
|
1720
|
+
contains all the validation errors. This method is used for checking the
|
1721
|
+
validity of the model silently.
|
1722
|
+
|
1723
|
+
* The `validate!` method raises a `Lutaml::Model::ValidationError` that contains
|
1724
|
+
all the validation errors. This method is used for forceful validation of the
|
1725
|
+
model through raising an error.
|
1726
|
+
|
1727
|
+
Lutaml::Model supports the following validation methods:
|
1728
|
+
|
1729
|
+
* `collection`:: Validates collection size range.
|
1730
|
+
* `values`:: Validates the value of an attribute from a set of fixed values.
|
1731
|
+
|
1732
|
+
[example]
|
1733
|
+
====
|
1734
|
+
The following class will validate the `degree_settings` attribute to ensure that
|
1735
|
+
it has at least one element and that the `description` attribute is one of the
|
1736
|
+
values in the set `[one, two, three]`.
|
1737
|
+
|
1738
|
+
[source,ruby]
|
1739
|
+
----
|
1740
|
+
class Klin < Lutaml::Model::Serializable
|
1741
|
+
attribute :name, :string
|
1742
|
+
attribute :degree_settings, :integer, collection: (1..)
|
1743
|
+
attribute :description, :string, values: %w[one two three]
|
1744
|
+
|
1745
|
+
xml do
|
1746
|
+
map_element 'name', to: :name
|
1747
|
+
map_attribute 'degree_settings', to: :degree_settings
|
1748
|
+
end
|
1749
|
+
end
|
1750
|
+
|
1751
|
+
klin = Klin.new(name: "Klin", degree_settings: [100, 200, 300], description: "one")
|
1752
|
+
klin.validate
|
1753
|
+
# => []
|
1754
|
+
|
1755
|
+
klin = Klin.new(name: "Klin", degree_settings: [], description: "four")
|
1756
|
+
klin.validate
|
1757
|
+
# => [
|
1758
|
+
# #<Lutaml::Model::CollectionSizeError: degree_settings must have at least 1 element>,
|
1759
|
+
# #<Lutaml::Model::ValueError: description must be one of [one, two, three]>
|
1760
|
+
# ]
|
1761
|
+
|
1762
|
+
e = klin.validate!
|
1763
|
+
# => Lutaml::Model::ValidationError: [
|
1764
|
+
# degree_settings must have at least 1 element,
|
1765
|
+
# description must be one of [one, two, three]
|
1766
|
+
# ]
|
1767
|
+
e.errors
|
1768
|
+
# => [
|
1769
|
+
# #<Lutaml::Model::CollectionSizeError: degree_settings must have at least 1 element>,
|
1770
|
+
# #<Lutaml::Model::ValueError: description must be one of [one, two, three]>
|
1771
|
+
# ]
|
1772
|
+
----
|
1773
|
+
====
|
1774
|
+
|
1775
|
+
=== Custom validation
|
1776
|
+
|
1777
|
+
To add custom validation, override the `validate` method in the model class.
|
1778
|
+
Additional errors should be added to the `errors` array.
|
1779
|
+
|
1780
|
+
[example]
|
1781
|
+
====
|
1782
|
+
The following class validates the `degree_settings` attribute when the `type` is
|
1783
|
+
`glass` to ensure that the value is less than 1300.
|
1784
|
+
|
1785
|
+
[source,ruby]
|
1786
|
+
----
|
1787
|
+
class Klin < Lutaml::Model::Serializable
|
1788
|
+
attribute :name, :string
|
1789
|
+
attribute :type, :string, values: %w[glass ceramic]
|
1790
|
+
attribute :degree_settings, :integer, collection: (1..)
|
1791
|
+
|
1792
|
+
def validate
|
1793
|
+
errors = super
|
1794
|
+
if type == "glass" && degree_settings.any? { |d| d > 1300 }
|
1795
|
+
errors << Lutaml::Model::Error.new("Degree settings for glass must be less than 1300")
|
1796
|
+
end
|
1797
|
+
end
|
1798
|
+
end
|
1799
|
+
|
1800
|
+
klin = Klin.new(name: "Klin", type: "glass", degree_settings: [100, 200, 1400])
|
1801
|
+
klin.validate
|
1802
|
+
# => [#<Lutaml::Model::Error: Degree settings for glass must be less than 1300>]
|
1803
|
+
----
|
1804
|
+
====
|
1805
|
+
|
1806
|
+
|
1550
1807
|
== Adapters
|
1551
1808
|
|
1552
1809
|
=== General
|
@@ -6,11 +6,11 @@ module Lutaml
|
|
6
6
|
def initialize(name, type, options = {})
|
7
7
|
@name = name
|
8
8
|
@type = cast_type(type)
|
9
|
-
|
10
9
|
@options = options
|
11
10
|
|
12
|
-
if collection?
|
13
|
-
|
11
|
+
if collection?
|
12
|
+
validate_collection_range
|
13
|
+
@options[:default] = -> { [] } unless options[:default]
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -31,6 +31,10 @@ module Lutaml
|
|
31
31
|
options[:collection] || false
|
32
32
|
end
|
33
33
|
|
34
|
+
def singular?
|
35
|
+
!collection?
|
36
|
+
end
|
37
|
+
|
34
38
|
def default
|
35
39
|
return options[:default].call if options[:default].is_a?(Proc)
|
36
40
|
|
@@ -41,6 +45,113 @@ module Lutaml
|
|
41
45
|
options.fetch(:render_nil, false)
|
42
46
|
end
|
43
47
|
|
48
|
+
def enum_values
|
49
|
+
@options.key?(:values) ? @options[:values] : []
|
50
|
+
end
|
51
|
+
|
52
|
+
# Check if the value to be assigned is valid for the attribute
|
53
|
+
#
|
54
|
+
# Currently there are 2 validations
|
55
|
+
# 1. Value should be from the values list if they are defined
|
56
|
+
# e.g values: ["foo", "bar"] is set then any other value for this
|
57
|
+
# attribute will raise `Lutaml::Model::InvalidValueError`
|
58
|
+
#
|
59
|
+
# 2. Value count should be between the collection range if defined
|
60
|
+
# e.g if collection: 0..5 is set then the value greater then 5
|
61
|
+
# will raise `Lutaml::Model::CollectionCountOutOfRangeError`
|
62
|
+
def validate_value!(value)
|
63
|
+
valid_value!(value)
|
64
|
+
valid_collection!(value)
|
65
|
+
end
|
66
|
+
|
67
|
+
def valid_value!(value)
|
68
|
+
return true if value.nil? && !collection?
|
69
|
+
return true if enum_values.empty?
|
70
|
+
|
71
|
+
unless valid_value?(value)
|
72
|
+
raise Lutaml::Model::InvalidValueError.new(name, value, enum_values)
|
73
|
+
end
|
74
|
+
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
def valid_value?(value)
|
79
|
+
return true unless options[:values]
|
80
|
+
|
81
|
+
options[:values].include?(value)
|
82
|
+
end
|
83
|
+
|
84
|
+
def validate_value!(value)
|
85
|
+
# return true if none of the validations are present
|
86
|
+
return true if enum_values.empty? && singular?
|
87
|
+
|
88
|
+
# Use the default value if the value is nil
|
89
|
+
value = default if value.nil?
|
90
|
+
|
91
|
+
valid_value!(value) && valid_collection!(value)
|
92
|
+
end
|
93
|
+
|
94
|
+
def validate_collection_range
|
95
|
+
range = @options[:collection]
|
96
|
+
return if range == true
|
97
|
+
|
98
|
+
unless range.is_a?(Range)
|
99
|
+
raise ArgumentError, "Invalid collection range: #{range}"
|
100
|
+
end
|
101
|
+
|
102
|
+
if range.begin.nil?
|
103
|
+
raise ArgumentError,
|
104
|
+
"Invalid collection range: #{range}. Begin must be specified."
|
105
|
+
end
|
106
|
+
|
107
|
+
if range.begin.negative?
|
108
|
+
raise ArgumentError,
|
109
|
+
"Invalid collection range: #{range}. Begin must be non-negative."
|
110
|
+
end
|
111
|
+
|
112
|
+
if range.end && range.end < range.begin
|
113
|
+
raise ArgumentError,
|
114
|
+
"Invalid collection range: #{range}. End must be greater than or equal to begin."
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def valid_collection!(value)
|
119
|
+
return true unless collection?
|
120
|
+
|
121
|
+
# Allow nil values for collections during initialization
|
122
|
+
return true if value.nil?
|
123
|
+
|
124
|
+
# Allow any value for unbounded collections
|
125
|
+
return true if options[:collection] == true
|
126
|
+
|
127
|
+
unless value.is_a?(Array)
|
128
|
+
raise Lutaml::Model::CollectionCountOutOfRangeError.new(
|
129
|
+
name,
|
130
|
+
value,
|
131
|
+
options[:collection],
|
132
|
+
)
|
133
|
+
end
|
134
|
+
|
135
|
+
range = options[:collection]
|
136
|
+
return true unless range.is_a?(Range)
|
137
|
+
|
138
|
+
if range.end.nil?
|
139
|
+
if value.size < range.begin
|
140
|
+
raise Lutaml::Model::CollectionCountOutOfRangeError.new(
|
141
|
+
name,
|
142
|
+
value,
|
143
|
+
range,
|
144
|
+
)
|
145
|
+
end
|
146
|
+
elsif !range.cover?(value.size)
|
147
|
+
raise Lutaml::Model::CollectionCountOutOfRangeError.new(
|
148
|
+
name,
|
149
|
+
value,
|
150
|
+
range,
|
151
|
+
)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
44
155
|
def serialize(value, format, options = {})
|
45
156
|
if value.is_a?(Array)
|
46
157
|
value.map do |v|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Lutaml
|
2
|
+
module Model
|
3
|
+
class CollectionCountOutOfRangeError < Error
|
4
|
+
def initialize(attr_name, value, range)
|
5
|
+
@attr_name = attr_name
|
6
|
+
@value = value
|
7
|
+
@range = range
|
8
|
+
|
9
|
+
super()
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
"#{@attr_name} count is #{@value.size}, must be #{range_to_string}"
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def range_to_string
|
19
|
+
if @range.end.nil?
|
20
|
+
"at least #{@range.begin}"
|
21
|
+
elsif @range.begin == @range.end
|
22
|
+
"exactly #{@range.begin}"
|
23
|
+
else
|
24
|
+
"between #{@range.begin} and #{@range.end}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# lib/lutaml/model/error/validation_error.rb
|
2
|
+
module Lutaml
|
3
|
+
module Model
|
4
|
+
class ValidationError < Error
|
5
|
+
attr_reader :errors
|
6
|
+
|
7
|
+
def initialize(errors)
|
8
|
+
@errors = errors
|
9
|
+
super(errors.join(", "))
|
10
|
+
end
|
11
|
+
|
12
|
+
def include?(error_class)
|
13
|
+
errors.any?(error_class)
|
14
|
+
end
|
15
|
+
|
16
|
+
def error_messages
|
17
|
+
errors.map(&:message)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/lutaml/model/error.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Lutaml
|
2
|
+
module Model
|
3
|
+
class Location
|
4
|
+
attr_reader :namespace, :location
|
5
|
+
|
6
|
+
def initialize(namespace:, location:)
|
7
|
+
@namespace = namespace
|
8
|
+
@location = location
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_xml_attribute
|
12
|
+
"#{@namespace} #{@location}".strip
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class SchemaLocation
|
17
|
+
DEFAULT_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance".freeze
|
18
|
+
|
19
|
+
attr_reader :namespace, :prefix, :schema_location
|
20
|
+
|
21
|
+
def initialize(schema_location:, prefix: "xsi",
|
22
|
+
namespace: DEFAULT_NAMESPACE)
|
23
|
+
@original_schema_location = schema_location
|
24
|
+
@schema_location = parsed_schema_locations(schema_location)
|
25
|
+
@prefix = prefix
|
26
|
+
@namespace = namespace
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_xml_attributes
|
30
|
+
{
|
31
|
+
"xmlns:#{prefix}" => namespace,
|
32
|
+
"#{prefix}:schemaLocation" => schema_location.map(&:to_xml_attribute).join(" "),
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](index)
|
37
|
+
@schema_location[index]
|
38
|
+
end
|
39
|
+
|
40
|
+
def size
|
41
|
+
@schema_location.size
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def parsed_schema_locations(schema_location)
|
47
|
+
locations = if schema_location.is_a?(Hash)
|
48
|
+
schema_location
|
49
|
+
else
|
50
|
+
schema_location.split.each_slice(2)
|
51
|
+
end
|
52
|
+
|
53
|
+
locations.map do |n, l|
|
54
|
+
Location.new(namespace: n, location: l)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -9,11 +9,14 @@ require_relative "xml_mapping"
|
|
9
9
|
require_relative "key_value_mapping"
|
10
10
|
require_relative "json_adapter"
|
11
11
|
require_relative "comparable_model"
|
12
|
+
require_relative "schema_location"
|
13
|
+
require_relative "validation"
|
12
14
|
|
13
15
|
module Lutaml
|
14
16
|
module Model
|
15
17
|
module Serialize
|
16
18
|
include ComparableModel
|
19
|
+
include Validation
|
17
20
|
|
18
21
|
def self.included(base)
|
19
22
|
base.extend(ClassMethods)
|
@@ -52,25 +55,10 @@ module Lutaml
|
|
52
55
|
|
53
56
|
define_method(:"#{name}=") do |value|
|
54
57
|
instance_variable_set(:"@#{name}", value)
|
55
|
-
validate
|
58
|
+
# validate!(name)
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
59
|
-
# Check if the value to be assigned is valid for the attribute
|
60
|
-
def attr_value_valid?(name, value)
|
61
|
-
attr = attributes[name]
|
62
|
-
|
63
|
-
return true unless attr.options[:values]
|
64
|
-
|
65
|
-
# Allow nil values if there's no default
|
66
|
-
return true if value.nil? && !attr.default
|
67
|
-
|
68
|
-
# Use the default value if the value is nil
|
69
|
-
value = attr.default if value.nil?
|
70
|
-
|
71
|
-
attr.options[:values].include?(value)
|
72
|
-
end
|
73
|
-
|
74
62
|
Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
|
75
63
|
define_method(format) do |&block|
|
76
64
|
klass = format == :xml ? XmlMapping : KeyValueMapping
|
@@ -92,8 +80,8 @@ module Lutaml
|
|
92
80
|
define_method(:"of_#{format}") do |hash|
|
93
81
|
if hash.is_a?(Array)
|
94
82
|
return hash.map do |item|
|
95
|
-
|
96
|
-
|
83
|
+
apply_mappings(item, format)
|
84
|
+
end
|
97
85
|
end
|
98
86
|
|
99
87
|
apply_mappings(hash, format)
|
@@ -316,6 +304,14 @@ module Lutaml
|
|
316
304
|
instance.ordered = mappings_for(:xml).mixed_content? || options[:mixed_content]
|
317
305
|
end
|
318
306
|
|
307
|
+
if doc["__schema_location"]
|
308
|
+
instance.schema_location = Lutaml::Model::SchemaLocation.new(
|
309
|
+
schema_location: doc["__schema_location"][:schema_location],
|
310
|
+
prefix: doc["__schema_location"][:prefix],
|
311
|
+
namespace: doc["__schema_location"][:namespace],
|
312
|
+
)
|
313
|
+
end
|
314
|
+
|
319
315
|
mappings.each do |rule|
|
320
316
|
attr = attributes[rule.to]
|
321
317
|
raise "Attribute '#{rule.to}' not found in #{self}" unless attr
|
@@ -376,9 +372,11 @@ module Lutaml
|
|
376
372
|
end
|
377
373
|
end
|
378
374
|
|
379
|
-
attr_accessor :element_order
|
375
|
+
attr_accessor :element_order, :schema_location
|
380
376
|
|
381
377
|
def initialize(attrs = {})
|
378
|
+
@validate_on_set = attrs.delete(:validate_on_set) || false
|
379
|
+
|
382
380
|
return unless self.class.attributes
|
383
381
|
|
384
382
|
if attrs.is_a?(Lutaml::Model::MappingHash)
|
@@ -386,13 +384,41 @@ module Lutaml
|
|
386
384
|
@element_order = attrs.item_order
|
387
385
|
end
|
388
386
|
|
387
|
+
if attrs.key?(:schema_location)
|
388
|
+
self.schema_location = attrs[:schema_location]
|
389
|
+
end
|
390
|
+
|
389
391
|
self.class.attributes.each do |name, attr|
|
390
|
-
value =
|
392
|
+
value = if attrs.key?(name) || attrs.key?(name.to_s)
|
393
|
+
self.class.attr_value(attrs, name, attr)
|
394
|
+
else
|
395
|
+
attr.default
|
396
|
+
end
|
391
397
|
|
392
|
-
|
398
|
+
# Initialize collections with an empty array if no value is provided
|
399
|
+
if attr.collection? && value.nil?
|
400
|
+
value = []
|
401
|
+
end
|
402
|
+
|
403
|
+
instance_variable_set(:"@#{name}", self.class.ensure_utf8(value))
|
393
404
|
end
|
405
|
+
end
|
394
406
|
|
395
|
-
|
407
|
+
def method_missing(method_name, *args)
|
408
|
+
if method_name.to_s.end_with?("=") && self.class.attributes.key?(method_name.to_s.chomp("=").to_sym)
|
409
|
+
define_singleton_method(method_name) do |value|
|
410
|
+
instance_variable_set(:"@#{method_name.to_s.chomp('=')}", value)
|
411
|
+
end
|
412
|
+
send(method_name, *args)
|
413
|
+
else
|
414
|
+
super
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
def validate_attribute!(attr_name)
|
419
|
+
attr = self.class.attributes[attr_name]
|
420
|
+
value = instance_variable_get(:"@#{attr_name}")
|
421
|
+
attr.validate_value!(value)
|
396
422
|
end
|
397
423
|
|
398
424
|
def ordered?
|
@@ -413,7 +439,7 @@ module Lutaml
|
|
413
439
|
|
414
440
|
Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format|
|
415
441
|
define_method(:"to_#{format}") do |options = {}|
|
416
|
-
validate
|
442
|
+
validate!
|
417
443
|
adapter = Lutaml::Model::Config.public_send(:"#{format}_adapter")
|
418
444
|
representation = if format == :xml
|
419
445
|
self
|
@@ -425,16 +451,6 @@ module Lutaml
|
|
425
451
|
adapter.new(representation).public_send(:"to_#{format}", options)
|
426
452
|
end
|
427
453
|
end
|
428
|
-
|
429
|
-
def validate
|
430
|
-
self.class.attributes.each do |name, attr|
|
431
|
-
value = send(name)
|
432
|
-
unless self.class.attr_value_valid?(name, value)
|
433
|
-
raise Lutaml::Model::InvalidValueError.new(name, value,
|
434
|
-
attr.options[:values])
|
435
|
-
end
|
436
|
-
end
|
437
|
-
end
|
438
454
|
end
|
439
455
|
end
|
440
456
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Lutaml
|
2
|
+
module Model
|
3
|
+
module Validation
|
4
|
+
def validate
|
5
|
+
errors = []
|
6
|
+
self.class.attributes.each do |name, attr|
|
7
|
+
value = instance_variable_get(:"@#{name}")
|
8
|
+
begin
|
9
|
+
attr.validate_value!(value)
|
10
|
+
rescue Lutaml::Model::InvalidValueError,
|
11
|
+
Lutaml::Model::CollectionCountOutOfRangeError => e
|
12
|
+
errors << e
|
13
|
+
end
|
14
|
+
end
|
15
|
+
errors
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate!
|
19
|
+
errors = validate
|
20
|
+
raise Lutaml::Model::ValidationError.new(errors) if errors.any?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/lutaml/model/version.rb
CHANGED
@@ -77,16 +77,23 @@ module Lutaml
|
|
77
77
|
element.children.each_with_object(result) do |child, hash|
|
78
78
|
value = child.text? ? child.text : parse_element(child)
|
79
79
|
|
80
|
-
if hash[child.unprefixed_name]
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
end
|
80
|
+
hash[child.unprefixed_name] = if hash[child.unprefixed_name]
|
81
|
+
[hash[child.unprefixed_name], value].flatten
|
82
|
+
else
|
83
|
+
value
|
84
|
+
end
|
86
85
|
end
|
87
86
|
|
88
87
|
element.attributes.each_value do |attr|
|
89
|
-
|
88
|
+
if attr.unprefixed_name == "schemaLocation"
|
89
|
+
result["__schema_location"] = {
|
90
|
+
namespace: attr.namespace,
|
91
|
+
prefix: attr.namespace_prefix,
|
92
|
+
schema_location: attr.value,
|
93
|
+
}
|
94
|
+
else
|
95
|
+
result[attr.unprefixed_name] = attr.value
|
96
|
+
end
|
90
97
|
end
|
91
98
|
|
92
99
|
result
|
@@ -150,6 +157,9 @@ module Lutaml
|
|
150
157
|
attributes = options[:xml_attributes] ||= {}
|
151
158
|
attributes = build_attributes(element,
|
152
159
|
xml_mapping, options).merge(attributes)&.compact
|
160
|
+
if element.respond_to?(:schema_location) && element.schema_location
|
161
|
+
attributes.merge!(element.schema_location.to_xml_attributes)
|
162
|
+
end
|
153
163
|
|
154
164
|
prefix = if options.key?(:namespace_prefix)
|
155
165
|
options[:namespace_prefix]
|
data/lutaml-model.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lutaml-model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ribose Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-09-
|
11
|
+
date: 2024-09-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bigdecimal
|
@@ -69,8 +69,10 @@ files:
|
|
69
69
|
- lib/lutaml/model/comparison.rb
|
70
70
|
- lib/lutaml/model/config.rb
|
71
71
|
- lib/lutaml/model/error.rb
|
72
|
+
- lib/lutaml/model/error/collection_count_out_of_range_error.rb
|
72
73
|
- lib/lutaml/model/error/invalid_value_error.rb
|
73
74
|
- lib/lutaml/model/error/unknown_adapter_type_error.rb
|
75
|
+
- lib/lutaml/model/error/validation_error.rb
|
74
76
|
- lib/lutaml/model/json_adapter.rb
|
75
77
|
- lib/lutaml/model/json_adapter/json_document.rb
|
76
78
|
- lib/lutaml/model/json_adapter/json_object.rb
|
@@ -86,6 +88,7 @@ files:
|
|
86
88
|
- lib/lutaml/model/schema/relaxng_schema.rb
|
87
89
|
- lib/lutaml/model/schema/xsd_schema.rb
|
88
90
|
- lib/lutaml/model/schema/yaml_schema.rb
|
91
|
+
- lib/lutaml/model/schema_location.rb
|
89
92
|
- lib/lutaml/model/serializable.rb
|
90
93
|
- lib/lutaml/model/serialize.rb
|
91
94
|
- lib/lutaml/model/toml_adapter.rb
|
@@ -97,6 +100,7 @@ files:
|
|
97
100
|
- lib/lutaml/model/type/date_time.rb
|
98
101
|
- lib/lutaml/model/type/time_without_date.rb
|
99
102
|
- lib/lutaml/model/utils.rb
|
103
|
+
- lib/lutaml/model/validation.rb
|
100
104
|
- lib/lutaml/model/version.rb
|
101
105
|
- lib/lutaml/model/xml_adapter.rb
|
102
106
|
- lib/lutaml/model/xml_adapter/builder/nokogiri.rb
|
@@ -119,7 +123,8 @@ files:
|
|
119
123
|
homepage: https://github.com/lutaml/lutaml-model
|
120
124
|
licenses:
|
121
125
|
- BSD-2-Clause
|
122
|
-
metadata:
|
126
|
+
metadata:
|
127
|
+
rubygems_mfa_required: 'true'
|
123
128
|
post_install_message:
|
124
129
|
rdoc_options: []
|
125
130
|
require_paths:
|