shale 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60a0f059693d5ea371ffca8b475ebb261ee99096509e588d2f1163a96bf8fbc0
4
- data.tar.gz: c1af0c61c7d9a93bd601188c50916d4f702228052541adee34456c4d7dac8b9a
3
+ metadata.gz: 81b22181e1c63b2757fdd137f09a185caf894de6d43c108119c18c9260b48afd
4
+ data.tar.gz: 065bc2d6f8a6eec466b52574ed38846dc0b4e0315ba9b83fc0d24ed75066ef07
5
5
  SHA512:
6
- metadata.gz: 31bd59c352febaf287eda07a79f56e5fe942fd86623f532e48c25ff5e946bb53fc879c3f8bb13121989268057beb4ee740f8aac7b1275dda8b9a2d21a5f75aaf
7
- data.tar.gz: 127b46cdeb0960643aa60410597f7ace1815dd19e3845cd0cb7c2ea3d2d44b4326333656bdf96209effcf17daabbc5a70de9f430acd42e21d961e2c356d9be9f
6
+ metadata.gz: 9b106ac74372bc089093ee8c12238b48505cff4d318967e2760ef2842e9a783125227177fd5f2cb47b27872adc7cbbf0686226934132b5c8f346c71b92159c8a
7
+ data.tar.gz: dcb0088d6f700f1654313a72156e9d1e4cd0c1b75b99a82cea17e12af96ced1407e358b1d0cc52e7e3f0a1c46cb19d702b3a49628a0da67e521a20906e006a6c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [1.2.0] - 2024-10-31
2
+
3
+ ### Added
4
+ - Allow to pass adapter specific options
5
+ - Allow to pass `additional_properties` option to JSON Schema generator
6
+ - Allow to pass `description` option to JSON Schema generator
7
+ - Support for symbol type aliases in attribute mappings
8
+
1
9
  ## [1.1.0] - 2024-02-17
2
10
 
3
11
  ### Added
data/README.md CHANGED
@@ -65,6 +65,7 @@ $ gem install shale
65
65
  * [Using methods to extract and generate data](#using-methods-to-extract-and-generate-data)
66
66
  * [Delegating fields to child attributes](#delegating-fields-to-child-attributes)
67
67
  * [Additional options](#additional-options)
68
+ * [Overriding attribute methods](#overriding-attribute-methods)
68
69
  * [Using custom models](#using-custom-models)
69
70
  * [Supported types](#supported-types)
70
71
  * [Writing your own type](#writing-your-own-type)
@@ -82,17 +83,17 @@ $ gem install shale
82
83
  require 'shale'
83
84
 
84
85
  class Address < Shale::Mapper
85
- attribute :city, Shale::Type::String
86
- attribute :street, Shale::Type::String
87
- attribute :zip, Shale::Type::String
86
+ attribute :city, :string
87
+ attribute :street, :string
88
+ attribute :zip, :string
88
89
  end
89
90
 
90
91
  class Person < Shale::Mapper
91
- attribute :first_name, Shale::Type::String
92
- attribute :last_name, Shale::Type::String
93
- attribute :age, Shale::Type::Integer
94
- attribute :married, Shale::Type::Boolean, default: -> { false }
95
- attribute :hobbies, Shale::Type::String, collection: true
92
+ attribute :first_name, :string
93
+ attribute :last_name, :string
94
+ attribute :age, :integer
95
+ attribute :married, :boolean, default: -> { false }
96
+ attribute :hobbies, :string, collection: true
96
97
  attribute :address, Address
97
98
  end
98
99
  ```
@@ -208,8 +209,8 @@ person.to_yaml
208
209
  ### Converting TOML to object
209
210
 
210
211
  To use TOML with Shale you have to set adapter you want to use.
211
- Out of the box Shale suports [Tomlib](https://github.com/kgiszczak/tomlib).
212
- It also comes with adapter for [toml-rb](https://github.com/emancu/toml-rb) if you prefer that.
212
+ It comes with adapters for [Tomlib](https://github.com/kgiszczak/tomlib) and
213
+ [toml-rb](https://github.com/emancu/toml-rb).
213
214
  For details see [Adapters](#adapters) section.
214
215
 
215
216
  To set it, first make sure Tomlib gem is installed:
@@ -221,8 +222,8 @@ $ gem install tomlib
221
222
  then setup adapter:
222
223
 
223
224
  ```ruby
224
- require 'tomlib'
225
- Shale.toml_adapter = Tomlib
225
+ require 'sahle/adapter/tomlib'
226
+ Shale.toml_adapter = Shale::Adapter::Tomlib
226
227
 
227
228
  # Alternatively if you'd like to use toml-rb, use:
228
229
  require 'shale/adapter/toml_rb'
@@ -442,8 +443,8 @@ By default keys are named the same as attributes. To use custom keys use:
442
443
 
443
444
  ```ruby
444
445
  class Person < Shale::Mapper
445
- attribute :first_name, Shale::Type::String
446
- attribute :last_name, Shale::Type::String
446
+ attribute :first_name, :string
447
+ attribute :last_name, :string
447
448
 
448
449
  json do
449
450
  map 'firstName', to: :first_name
@@ -456,8 +457,8 @@ end
456
457
 
457
458
  ```ruby
458
459
  class Person < Shale::Mapper
459
- attribute :first_name, Shale::Type::String
460
- attribute :last_name, Shale::Type::String
460
+ attribute :first_name, :string
461
+ attribute :last_name, :string
461
462
 
462
463
  yaml do
463
464
  map 'firstName', to: :first_name
@@ -470,8 +471,8 @@ end
470
471
 
471
472
  ```ruby
472
473
  class Person < Shale::Mapper
473
- attribute :first_name, Shale::Type::String
474
- attribute :last_name, Shale::Type::String
474
+ attribute :first_name, :string
475
+ attribute :last_name, :string
475
476
 
476
477
  toml do
477
478
  map 'firstName', to: :first_name
@@ -488,8 +489,8 @@ to `:first_name` attribute and the second column to `:last_name`.
488
489
 
489
490
  ```ruby
490
491
  class Person < Shale::Mapper
491
- attribute :first_name, Shale::Type::String
492
- attribute :last_name, Shale::Type::String
492
+ attribute :first_name, :string
493
+ attribute :last_name, :string
493
494
 
494
495
  csv do
495
496
  map 'firstName', to: :first_name
@@ -502,8 +503,8 @@ end
502
503
 
503
504
  ```ruby
504
505
  class Person < Shale::Mapper
505
- attribute :first_name, Shale::Type::String
506
- attribute :last_name, Shale::Type::String
506
+ attribute :first_name, :string
507
+ attribute :last_name, :string
507
508
 
508
509
  hsh do
509
510
  map 'firstName', to: :first_name
@@ -518,9 +519,9 @@ XML is more complicated format than JSON or YAML. To map elements, attributes an
518
519
 
519
520
  ```ruby
520
521
  class Address < Shale::Mapper
521
- attribute :street, Shale::Type::String
522
- attribute :city, Shale::Type::String
523
- attribute :zip, Shale::Type::String
522
+ attribute :street, :string
523
+ attribute :city, :string
524
+ attribute :zip, :string
524
525
 
525
526
  xml do
526
527
  map_content to: :street
@@ -530,10 +531,10 @@ class Address < Shale::Mapper
530
531
  end
531
532
 
532
533
  class Person < Shale::Mapper
533
- attribute :first_name, Shale::Type::String
534
- attribute :last_name, Shale::Type::String
535
- attribute :age, Shale::Type::Integer
536
- attribute :hobbies, Shale::Type::String, collection: true
534
+ attribute :first_name, :string
535
+ attribute :last_name, :string
536
+ attribute :age, :integer
537
+ attribute :hobbies, :string, collection: true
537
538
  attribute :address, Address
538
539
 
539
540
  xml do
@@ -572,7 +573,7 @@ You can use `cdata: true` option on `map_element` and `map_content` to handle CD
572
573
 
573
574
  ```ruby
574
575
  class Address < Shale::Mapper
575
- attribute :content, Shale::Type::String
576
+ attribute :content, :string
576
577
 
577
578
  xml do
578
579
  map_content to: :content, cdata: true
@@ -580,7 +581,7 @@ class Address < Shale::Mapper
580
581
  end
581
582
 
582
583
  class Person < Shale::Mapper
583
- attribute :first_name, Shale::Type::String
584
+ attribute :first_name, :string
584
585
  attribute :address, Address
585
586
 
586
587
  xml do
@@ -606,9 +607,9 @@ To map namespaced elements and attributes use `namespace` and `prefix` propertie
606
607
 
607
608
  ```ruby
608
609
  class Person < Shale::Mapper
609
- attribute :first_name, Shale::Type::String
610
- attribute :last_name, Shale::Type::String
611
- attribute :age, Shale::Type::Integer
610
+ attribute :first_name, :string
611
+ attribute :last_name, :string
612
+ attribute :age, :integer
612
613
 
613
614
  xml do
614
615
  root 'person'
@@ -633,11 +634,11 @@ explicitly declare it on `map_attribute`).
633
634
 
634
635
  ```ruby
635
636
  class Person < Shale::Mapper
636
- attribute :first_name, Shale::Type::String
637
- attribute :middle_name, Shale::Type::String
638
- attribute :last_name, Shale::Type::String
639
- attribute :age, Shale::Type::Integer
640
- attribute :hobby, Shale::Type::String
637
+ attribute :first_name, :string
638
+ attribute :middle_name, :string
639
+ attribute :last_name, :string
640
+ attribute :age, :integer
641
+ attribute :hobby, :string
641
642
 
642
643
  xml do
643
644
  root 'person'
@@ -673,9 +674,9 @@ For CSV the default is to render `nil` elements.
673
674
 
674
675
  ```ruby
675
676
  class Person < Shale::Mapper
676
- attribute :first_name, Shale::Type::String
677
- attribute :last_name, Shale::Type::String
678
- attribute :age, Shale::Type::Integer
677
+ attribute :first_name, :string
678
+ attribute :last_name, :string
679
+ attribute :age, :integer
679
680
 
680
681
  json do
681
682
  map 'first_name', to: :first_name, render_nil: true
@@ -723,9 +724,9 @@ class Base < Shale::Mapper
723
724
  end
724
725
 
725
726
  class Person < Base
726
- attribute :first_name, Shale::Type::String
727
- attribute :last_name, Shale::Type::String
728
- attribute :age, Shale::Type::Integer
727
+ attribute :first_name, :string
728
+ attribute :last_name, :string
729
+ attribute :age, :integer
729
730
 
730
731
  json do
731
732
  # override default from Base class
@@ -742,8 +743,8 @@ end
742
743
 
743
744
  ```ruby
744
745
  class Person < Base
745
- attribute :first_name, Shale::Type::String
746
- attribute :last_name, Shale::Type::String
746
+ attribute :first_name, :string
747
+ attribute :last_name, :string
747
748
 
748
749
  json do
749
750
  render_nil false
@@ -762,9 +763,9 @@ you can use methods to do so:
762
763
 
763
764
  ```ruby
764
765
  class Person < Shale::Mapper
765
- attribute :hobbies, Shale::Type::String, collection: true
766
- attribute :street, Shale::Type::String
767
- attribute :city, Shale::Type::String
766
+ attribute :hobbies, :string, collection: true
767
+ attribute :street, :string
768
+ attribute :city, :string
768
769
 
769
770
  json do
770
771
  map 'hobbies', using: { from: :hobbies_from_json, to: :hobbies_to_json }
@@ -853,7 +854,7 @@ You can also pass a `context` object that will be available in extractor/generat
853
854
 
854
855
  ```ruby
855
856
  class Person < Shale::Mapper
856
- attribute :password, Shale::Type::String
857
+ attribute :password, :string
857
858
 
858
859
  json do
859
860
  map 'password', using: { from: :password_from_json, to: :password_to_json }
@@ -883,7 +884,7 @@ If you want to work on multiple elements at a time you can group them using `gro
883
884
 
884
885
  ```ruby
885
886
  class Person < Shale::Mapper
886
- attribute :name, Shale::Type::String
887
+ attribute :name, :string
887
888
 
888
889
  json do
889
890
  group from: :name_from_json, to: :name_to_json do
@@ -934,12 +935,12 @@ To delegate fields to child complex types you can use `receiver: :child` declara
934
935
 
935
936
  ```ruby
936
937
  class Address < Shale::Mapper
937
- attribute :city, Shale::Type::String
938
- attribute :street, Shale::Type::String
938
+ attribute :city, :string
939
+ attribute :street, :string
939
940
  end
940
941
 
941
942
  class Person < Shale::Mapper
942
- attribute :name, Shale::Type::String
943
+ attribute :name, :string
943
944
  attribute :address, Address
944
945
 
945
946
  json do
@@ -1059,8 +1060,8 @@ names and shouldn't be included in the returned collection. It also accepts all
1059
1060
 
1060
1061
  ```ruby
1061
1062
  class Person
1062
- attribute :first_name, Shale::Type::String
1063
- attribute :last_name, Shale::Type::String
1063
+ attribute :first_name, :string
1064
+ attribute :last_name, :string
1064
1065
  end
1065
1066
 
1066
1067
  people = Person.from_csv(<<~DATA, headers: true, col_sep: '|')
@@ -1085,6 +1086,63 @@ Person.to_csv(people, headers: true, col_sep: '|')
1085
1086
  # James|Sixpack
1086
1087
  ```
1087
1088
 
1089
+ Most adapters accept options specific to them. Eg. if you want to be able to work
1090
+ with NaN values in JSON:
1091
+
1092
+ ```ruby
1093
+ class Person
1094
+ attribute :age, :float
1095
+ end
1096
+
1097
+ person = Person.from_json('{"age": NaN}', allow_nan: true)
1098
+
1099
+ # =>
1100
+ #
1101
+ # #<Person:0x0000000113d7a488 @age=Float::NAN>
1102
+
1103
+ Person.to_json(person, allow_nan: true)
1104
+
1105
+ # =>
1106
+ #
1107
+ # {
1108
+ # "age": NaN
1109
+ # }
1110
+ ```
1111
+
1112
+ ### Overriding attribute methods
1113
+
1114
+ It's possible to override an attribute method to change its output:
1115
+
1116
+ ```ruby
1117
+ class Person < Shale::Mapper
1118
+ attribute :gender, :string
1119
+
1120
+ def gender
1121
+ if super == 'm'
1122
+ 'male'
1123
+ else
1124
+ 'female'
1125
+ end
1126
+ end
1127
+ end
1128
+
1129
+ puts Person.from_json('{"gender":"m"}')
1130
+
1131
+ # =>
1132
+ #
1133
+ # #<Person:0x00007f9bc3086d60
1134
+ # @gender="male">
1135
+ ```
1136
+
1137
+ Be conscious that the original attribute value will be lost after its transformation though:
1138
+
1139
+ ```ruby
1140
+ puts User.from_json('{"gender":"m"}').to_json
1141
+ # => {"gender":"male"}
1142
+ ```
1143
+
1144
+ It'll no longer return gender `m`.
1145
+
1088
1146
  ### Using custom models
1089
1147
 
1090
1148
  By default Shale combines mapper and model into one class. If you want to use your own classes
@@ -1102,15 +1160,15 @@ end
1102
1160
  class AddressMapper < Shale::Mapper
1103
1161
  model Address
1104
1162
 
1105
- attribute :street, Shale::Type::String
1106
- attribute :city, Shale::Type::String
1163
+ attribute :street, :string
1164
+ attribute :city, :string
1107
1165
  end
1108
1166
 
1109
1167
  class PersonMapper < Shale::Mapper
1110
1168
  model Person
1111
1169
 
1112
- attribute :first_name, Shale::Type::String
1113
- attribute :last_name, Shale::Type::String
1170
+ attribute :first_name, :string
1171
+ attribute :last_name, :string
1114
1172
  attribute :address, AddressMapper
1115
1173
  end
1116
1174
 
@@ -1150,12 +1208,21 @@ PersonMapper.to_json(person, pretty: true)
1150
1208
 
1151
1209
  Shale supports these types out of the box:
1152
1210
 
1153
- - `Shale::Type::Boolean`
1154
- - `Shale::Type::Date`
1155
- - `Shale::Type::Float`
1156
- - `Shale::Type::Integer`
1157
- - `Shale::Type::String`
1158
- - `Shale::Type::Time`
1211
+ - `:boolean` (`Shale::Type::Boolean`)
1212
+ - `:date` (`Shale::Type::Date`)
1213
+ - `:float` (`Shale::Type::Float`)
1214
+ - `:integer` (`Shale::Type::Integer`)
1215
+ - `:string` (`Shale::Type::String`)
1216
+ - `:time` (`Shale::Type::Time`)
1217
+
1218
+ The symbol type alias and the type class are interchangeable:
1219
+
1220
+ ```ruby
1221
+ class Person < Shale::Mapper
1222
+ attribute :age, Shale::Type::Integer
1223
+ # attribute :age, :integer
1224
+ end
1225
+ ```
1159
1226
 
1160
1227
  ### Writing your own type
1161
1228
 
@@ -1171,6 +1238,31 @@ class MyIntegerType < Shale::Type::Value
1171
1238
  end
1172
1239
  ```
1173
1240
 
1241
+ Then you can use it in your model:
1242
+
1243
+ ```ruby
1244
+ class Person < Shale::Mapper
1245
+ attribute :age, MyIntegerType
1246
+ end
1247
+ ```
1248
+
1249
+ You can also register your own type with a symbol alias if you
1250
+ intend to use it often.
1251
+
1252
+ ```ruby
1253
+ require 'shale/type'
1254
+
1255
+ Shale::Type.register(:my_integer, MyIntegerType)
1256
+ ```
1257
+
1258
+ Then you can use it like this:
1259
+
1260
+ ```ruby
1261
+ class Person < Shale::Mapper
1262
+ attribute :age, :my_integer
1263
+ end
1264
+ ```
1265
+
1174
1266
  ### Adapters
1175
1267
 
1176
1268
  Shale uses adapters for parsing and generating documents.
@@ -1323,18 +1415,18 @@ require 'shale/schema'
1323
1415
  class PersonMapper < Shale::Mapper
1324
1416
  model Person
1325
1417
 
1326
- attribute :first_name, Shale::Type::String
1327
- attribute :last_name, Shale::Type::String
1328
- attribute :address, Shale::Type::String
1329
- attribute :age, Shale::Type::Integer
1418
+ attribute :first_name, :string
1419
+ attribute :last_name, :string
1420
+ attribute :address, :string
1421
+ attribute :age, :integer
1330
1422
 
1331
1423
  json do
1332
- properties max_properties: 5
1424
+ properties max_properties: 5, additional_properties: false
1333
1425
 
1334
1426
  map "first_name", to: :first_name, schema: { required: true }
1335
1427
  map "last_name", to: :last_name, schema: { required: true }
1336
- map "address", to: :age, schema: { max_length: 128 }
1337
- map "age", to: :age, schema: { minimum: 1, maximum: 150 }
1428
+ map "address", to: :address, schema: { max_length: 128, description: "Street, home number, city and country" }
1429
+ map "age", to: :age, schema: { minimum: 1, maximum: 150, description: "Person age" }
1338
1430
  end
1339
1431
  end
1340
1432
 
@@ -1352,7 +1444,6 @@ Shale::Schema.to_json(
1352
1444
  # "$defs": {
1353
1445
  # "Person": {
1354
1446
  # "type": "object",
1355
- # "maxProperties": 5,
1356
1447
  # "properties": {
1357
1448
  # "first_name": {
1358
1449
  # "type": "string"
@@ -1360,23 +1451,30 @@ Shale::Schema.to_json(
1360
1451
  # "last_name": {
1361
1452
  # "type": "string"
1362
1453
  # },
1363
- # "age": {
1454
+ # "address": {
1364
1455
  # "type": [
1365
- # "integer",
1456
+ # "string",
1366
1457
  # "null"
1367
1458
  # ],
1368
- # "minimum": 1,
1369
- # "maximum": 150
1370
- # },
1371
- # "address": {
1459
+ # "maxLength": 128,
1460
+ # "description": "Street, home number, city and country"
1461
+ # },
1462
+ # "age": {
1372
1463
  # "type": [
1373
- # "string",
1464
+ # "integer",
1374
1465
  # "null"
1375
1466
  # ],
1376
- # "maxLength": 128
1467
+ # "minimum": 1,
1468
+ # "maximum": 150,
1469
+ # "description": "Person age"
1377
1470
  # }
1378
1471
  # },
1379
- # "required": ["first_name", "last_name"]
1472
+ # "required": [
1473
+ # "first_name",
1474
+ # "last_name"
1475
+ # ],
1476
+ # "maxProperties": 5,
1477
+ # "additionalProperties": false
1380
1478
  # }
1381
1479
  # }
1382
1480
  # }
@@ -11,27 +11,30 @@ module Shale
11
11
  # Parse JSON into Hash
12
12
  #
13
13
  # @param [String] json JSON document
14
+ # @param [Hash] options
14
15
  #
15
16
  # @return [Hash]
16
17
  #
17
18
  # @api private
18
- def self.load(json)
19
- ::JSON.parse(json)
19
+ def self.load(json, **options)
20
+ ::JSON.parse(json, **options)
20
21
  end
21
22
 
22
23
  # Serialize Hash into JSON
23
24
  #
24
25
  # @param [Hash] obj Hash object
25
- # @param [true, false] pretty
26
+ # @param [Hash] options
26
27
  #
27
28
  # @return [String]
28
29
  #
29
30
  # @api private
30
- def self.dump(obj, pretty: false)
31
- if pretty
32
- ::JSON.pretty_generate(obj)
31
+ def self.dump(obj, **options)
32
+ json_options = options.except(:pretty)
33
+
34
+ if options[:pretty]
35
+ ::JSON.pretty_generate(obj, **json_options)
33
36
  else
34
- ::JSON.generate(obj)
37
+ ::JSON.generate(obj, **json_options)
35
38
  end
36
39
  end
37
40
  end
@@ -4,29 +4,31 @@ require 'toml-rb'
4
4
 
5
5
  module Shale
6
6
  module Adapter
7
- # TOML adapter
7
+ # TomlRB adapter
8
8
  #
9
9
  # @api public
10
10
  class TomlRB
11
11
  # Parse TOML into Hash
12
12
  #
13
13
  # @param [String] toml TOML document
14
+ # @param [Hash] options
14
15
  #
15
16
  # @return [Hash]
16
17
  #
17
18
  # @api private
18
- def self.load(toml)
19
+ def self.load(toml, **_options)
19
20
  ::TomlRB.parse(toml)
20
21
  end
21
22
 
22
23
  # Serialize Hash into TOML
23
24
  #
24
25
  # @param [Hash] obj Hash object
26
+ # @param [Hash] options
25
27
  #
26
28
  # @return [String]
27
29
  #
28
30
  # @api private
29
- def self.dump(obj)
31
+ def self.dump(obj, **_options)
30
32
  ::TomlRB.dump(obj)
31
33
  end
32
34
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tomlib'
4
+
5
+ module Shale
6
+ module Adapter
7
+ # Tomlib adapter
8
+ #
9
+ # @api public
10
+ class Tomlib
11
+ # Parse TOML into Hash
12
+ #
13
+ # @param [String] toml TOML document
14
+ # @param [Hash] options
15
+ #
16
+ # @return [Hash]
17
+ #
18
+ # @api private
19
+ def self.load(toml, **_options)
20
+ ::Tomlib.load(toml)
21
+ end
22
+
23
+ # Serialize Hash into TOML
24
+ #
25
+ # @param [Hash] obj Hash object
26
+ # @param [Hash] options
27
+ #
28
+ # @return [String]
29
+ #
30
+ # @api private
31
+ def self.dump(obj, **options)
32
+ ::Tomlib.dump(obj, **options)
33
+ end
34
+ end
35
+ end
36
+ end
data/lib/shale/error.rb CHANGED
@@ -9,7 +9,8 @@ module Shale
9
9
 
10
10
  # To use Tomlib:
11
11
  # Make sure tomlib is installed eg. execute: gem install tomlib
12
- Shale.toml_adapter = Tomlib
12
+ require 'shale/adapter/tomlib'
13
+ Shale.toml_adapter = Shale::Adapter::Tomlib
13
14
 
14
15
  # To use toml-rb:
15
16
  # Make sure toml-rb is installed eg. execute: gem install toml-rb
@@ -104,6 +105,18 @@ module Shale
104
105
  class NotAShaleMapperError < ShaleError
105
106
  end
106
107
 
108
+ # Error for registering class that is not a valid Type::Value
109
+ #
110
+ # @api private
111
+ class NotATypeValueError < ShaleError
112
+ end
113
+
114
+ # Error for using unknown symbol type
115
+ #
116
+ # @api private
117
+ class UnknownTypeError < ShaleError
118
+ end
119
+
107
120
  # Raised when receiver attribute is not defined
108
121
  #
109
122
  # @api private
data/lib/shale/mapper.rb CHANGED
@@ -5,6 +5,7 @@ require_relative 'error'
5
5
  require_relative 'utils'
6
6
  require_relative 'mapping/dict'
7
7
  require_relative 'mapping/xml'
8
+ require_relative 'type'
8
9
  require_relative 'type/complex'
9
10
 
10
11
  module Shale
@@ -12,16 +13,16 @@ module Shale
12
13
  #
13
14
  # @example
14
15
  # class Address < Shale::Mapper
15
- # attribute :city, Shale::Type::String
16
- # attribute :street, Shale::Type::String
17
- # attribute :state, Shale::Type::Integer
18
- # attribute :zip, Shale::Type::String
16
+ # attribute :city, :string
17
+ # attribute :street, :string
18
+ # attribute :state, :string
19
+ # attribute :zip, :string
19
20
  # end
20
21
  #
21
22
  # class Person < Shale::Mapper
22
- # attribute :first_name, Shale::Type::String
23
- # attribute :last_name, Shale::Type::String
24
- # attribute :age, Shale::Type::Integer
23
+ # attribute :first_name, :string
24
+ # attribute :last_name, :string
25
+ # attribute :age, :integer
25
26
  # attribute :address, Address
26
27
  # end
27
28
  #
@@ -144,18 +145,19 @@ module Shale
144
145
  # Define attribute on class
145
146
  #
146
147
  # @param [Symbol] name Name of the attribute
147
- # @param [Shale::Type::Value] type Type of the attribute
148
+ # @param [Symbol, Class<Shale::Type::Value>] type Type of the attribute
148
149
  # @param [Boolean] collection Is the attribute a collection
149
150
  # @param [Proc] default Default value for the attribute
150
151
  #
151
152
  # @raise [DefaultNotCallableError] when attribute's default is not callable
153
+ # @raise [UnknownTypeError] when type is a symbol and not found in the registry
152
154
  #
153
155
  # @example
154
156
  # class Person < Shale::Mapper
155
- # attribute :first_name, Shale::Type::String
156
- # attribute :last_name, Shale::Type::String
157
- # attribute :age, Shale::Type::Integer, default: -> { 1 }
158
- # attribute :hobbies, Shale::Type::String, collection: true
157
+ # attribute :first_name, :string
158
+ # attribute :last_name, :string
159
+ # attribute :age, :integer, default: -> { 1 }
160
+ # attribute :hobbies, :string, collection: true
159
161
  # end
160
162
  #
161
163
  # person = Person.new
@@ -177,6 +179,10 @@ module Shale
177
179
  raise DefaultNotCallableError.new(to_s, name)
178
180
  end
179
181
 
182
+ if type.is_a?(Symbol)
183
+ type = Type.lookup(type)
184
+ end
185
+
180
186
  @attributes[name] = Attribute.new(name, type, collection, default)
181
187
 
182
188
  @hash_mapping.map(name.to_s, to: name) unless @hash_mapping.finalized?
@@ -201,9 +207,9 @@ module Shale
201
207
  #
202
208
  # @example
203
209
  # class Person < Shale::Mapper
204
- # attribute :first_name, Shale::Type::String
205
- # attribute :last_name, Shale::Type::String
206
- # attribute :age, Shale::Type::Integer
210
+ # attribute :first_name, :string
211
+ # attribute :last_name, :string
212
+ # attribute :age, :integer
207
213
  #
208
214
  # hsh do
209
215
  # map 'firstName', to: :first_name
@@ -225,9 +231,9 @@ module Shale
225
231
  #
226
232
  # @example
227
233
  # class Person < Shale::Mapper
228
- # attribute :first_name, Shale::Type::String
229
- # attribute :last_name, Shale::Type::String
230
- # attribute :age, Shale::Type::Integer
234
+ # attribute :first_name, :string
235
+ # attribute :last_name, :string
236
+ # attribute :age, :integer
231
237
  #
232
238
  # json do
233
239
  # map 'firstName', to: :first_name
@@ -249,9 +255,9 @@ module Shale
249
255
  #
250
256
  # @example
251
257
  # class Person < Shale::Mapper
252
- # attribute :first_name, Shale::Type::String
253
- # attribute :last_name, Shale::Type::String
254
- # attribute :age, Shale::Type::Integer
258
+ # attribute :first_name, :string
259
+ # attribute :last_name, :string
260
+ # attribute :age, :integer
255
261
  #
256
262
  # yaml do
257
263
  # map 'first_name', to: :first_name
@@ -273,9 +279,9 @@ module Shale
273
279
  #
274
280
  # @example
275
281
  # class Person < Shale::Mapper
276
- # attribute :first_name, Shale::Type::String
277
- # attribute :last_name, Shale::Type::String
278
- # attribute :age, Shale::Type::Integer
282
+ # attribute :first_name, :string
283
+ # attribute :last_name, :string
284
+ # attribute :age, :integer
279
285
  #
280
286
  # toml do
281
287
  # map 'first_name', to: :first_name
@@ -297,9 +303,9 @@ module Shale
297
303
  #
298
304
  # @example
299
305
  # class Person < Shale::Mapper
300
- # attribute :first_name, Shale::Type::String
301
- # attribute :last_name, Shale::Type::String
302
- # attribute :age, Shale::Type::Integer
306
+ # attribute :first_name, :string
307
+ # attribute :last_name, :string
308
+ # attribute :age, :integer
303
309
  #
304
310
  # csv do
305
311
  # map 'first_name', to: :first_name
@@ -321,9 +327,9 @@ module Shale
321
327
  #
322
328
  # @example
323
329
  # class Person < Shale::Mapper
324
- # attribute :first_name, Shale::Type::String
325
- # attribute :last_name, Shale::Type::String
326
- # attribute :age, Shale::Type::Integer
330
+ # attribute :first_name, :string
331
+ # attribute :last_name, :string
332
+ # attribute :age, :integer
327
333
  #
328
334
  # xml do
329
335
  # root 'Person'
@@ -67,13 +67,15 @@ module Shale
67
67
  # @param [Integer] min_properties
68
68
  # @param [Integer] max_properties
69
69
  # @param [Hash] dependent_required
70
+ # @param [Boolean] additional_properties
70
71
  #
71
72
  # @api public
72
- def properties(min_properties: nil, max_properties: nil, dependent_required: nil)
73
+ def properties(min_properties: nil, max_properties: nil, dependent_required: nil, additional_properties: nil)
73
74
  @root = {
74
75
  min_properties: min_properties,
75
76
  max_properties: max_properties,
76
77
  dependent_required: dependent_required,
78
+ additional_properties: additional_properties,
77
79
  }
78
80
  end
79
81
 
@@ -15,7 +15,8 @@ module Shale
15
15
  #
16
16
  # @api private
17
17
  def as_type
18
- { 'type' => 'boolean' }
18
+ { 'type' => 'boolean',
19
+ 'description' => schema[:description] }.compact
19
20
  end
20
21
  end
21
22
  end
@@ -46,7 +46,8 @@ module Shale
46
46
  'maxItems' => schema[:max_items],
47
47
  'uniqueItems' => schema[:unique],
48
48
  'minContains' => schema[:min_contains],
49
- 'maxContains' => schema[:max_contains] }.compact
49
+ 'maxContains' => schema[:max_contains],
50
+ 'description' => schema[:description] }.compact
50
51
  end
51
52
  end
52
53
  end
@@ -15,7 +15,9 @@ module Shale
15
15
  #
16
16
  # @api private
17
17
  def as_type
18
- { 'type' => 'string', 'format' => 'date' }
18
+ { 'type' => 'string',
19
+ 'format' => 'date',
20
+ 'description' => schema[:description] }.compact
19
21
  end
20
22
  end
21
23
  end
@@ -20,7 +20,8 @@ module Shale
20
20
  'exclusiveMaximum' => schema[:exclusive_maximum],
21
21
  'minimum' => schema[:minimum],
22
22
  'maximum' => schema[:maximum],
23
- 'multipleOf' => schema[:multiple_of] }.compact
23
+ 'multipleOf' => schema[:multiple_of],
24
+ 'description' => schema[:description] }.compact
24
25
  end
25
26
  end
26
27
  end
@@ -20,7 +20,8 @@ module Shale
20
20
  'exclusiveMaximum' => schema[:exclusive_maximum],
21
21
  'minimum' => schema[:minimum],
22
22
  'maximum' => schema[:maximum],
23
- 'multipleOf' => schema[:multiple_of] }.compact
23
+ 'multipleOf' => schema[:multiple_of],
24
+ 'description' => schema[:description] }.compact
24
25
  end
25
26
  end
26
27
  end
@@ -40,6 +40,8 @@ module Shale
40
40
  'minProperties' => @root[:min_properties],
41
41
  'maxProperties' => @root[:max_properties],
42
42
  'dependentRequired' => @root[:dependent_required],
43
+ 'description' => @root[:description],
44
+ 'additionalProperties' => @root[:additional_properties],
43
45
  }.compact
44
46
  end
45
47
  end
@@ -19,7 +19,8 @@ module Shale
19
19
  'format' => schema[:format],
20
20
  'minLength' => schema[:min_length],
21
21
  'maxLength' => schema[:max_length],
22
- 'pattern' => schema[:pattern] }.compact
22
+ 'pattern' => schema[:pattern],
23
+ 'description' => schema[:description] }.compact
23
24
  end
24
25
  end
25
26
  end
@@ -15,7 +15,9 @@ module Shale
15
15
  #
16
16
  # @api private
17
17
  def as_type
18
- { 'type' => 'string', 'format' => 'date-time' }
18
+ { 'type' => 'string',
19
+ 'format' => 'date-time',
20
+ 'description' => schema[:description] }.compact
19
21
  end
20
22
  end
21
23
  end
@@ -15,7 +15,8 @@ module Shale
15
15
  #
16
16
  # @api private
17
17
  def as_type
18
- { 'type' => %w[boolean integer number object string] }
18
+ { 'type' => %w[boolean integer number object string],
19
+ 'description' => schema[:description] }.compact
19
20
  end
20
21
  end
21
22
  end
@@ -29,5 +29,7 @@ module Shale
29
29
  !FALSE_VALUES.include?(value) unless value.nil?
30
30
  end
31
31
  end
32
+
33
+ register(:boolean, Boolean)
32
34
  end
33
35
  end
@@ -257,13 +257,14 @@ module Shale
257
257
  # @param [Array<Symbol>] only
258
258
  # @param [Array<Symbol>] except
259
259
  # @param [any] context
260
+ # @param [Hash] json_options
260
261
  #
261
262
  # @return [model instance]
262
263
  #
263
264
  # @api public
264
- def from_json(json, only: nil, except: nil, context: nil)
265
+ def from_json(json, only: nil, except: nil, context: nil, **json_options)
265
266
  of_json(
266
- Shale.json_adapter.load(json),
267
+ Shale.json_adapter.load(json, **json_options),
267
268
  only: only,
268
269
  except: except,
269
270
  context: context
@@ -277,14 +278,15 @@ module Shale
277
278
  # @param [Array<Symbol>] except
278
279
  # @param [any] context
279
280
  # @param [true, false] pretty
281
+ # @param [Hash] json_options
280
282
  #
281
283
  # @return [String]
282
284
  #
283
285
  # @api public
284
- def to_json(instance, only: nil, except: nil, context: nil, pretty: false)
286
+ def to_json(instance, only: nil, except: nil, context: nil, pretty: false, **json_options)
285
287
  Shale.json_adapter.dump(
286
288
  as_json(instance, only: only, except: except, context: context),
287
- pretty: pretty
289
+ **json_options.merge(pretty: pretty)
288
290
  )
289
291
  end
290
292
 
@@ -294,13 +296,14 @@ module Shale
294
296
  # @param [Array<Symbol>] only
295
297
  # @param [Array<Symbol>] except
296
298
  # @param [any] context
299
+ # @param [Hash] yaml_options
297
300
  #
298
301
  # @return [model instance]
299
302
  #
300
303
  # @api public
301
- def from_yaml(yaml, only: nil, except: nil, context: nil)
304
+ def from_yaml(yaml, only: nil, except: nil, context: nil, **yaml_options)
302
305
  of_yaml(
303
- Shale.yaml_adapter.load(yaml),
306
+ Shale.yaml_adapter.load(yaml, **yaml_options),
304
307
  only: only,
305
308
  except: except,
306
309
  context: context
@@ -313,13 +316,15 @@ module Shale
313
316
  # @param [Array<Symbol>] only
314
317
  # @param [Array<Symbol>] except
315
318
  # @param [any] context
319
+ # @param [Hash] yaml_options
316
320
  #
317
321
  # @return [String]
318
322
  #
319
323
  # @api public
320
- def to_yaml(instance, only: nil, except: nil, context: nil)
324
+ def to_yaml(instance, only: nil, except: nil, context: nil, **yaml_options)
321
325
  Shale.yaml_adapter.dump(
322
- as_yaml(instance, only: only, except: except, context: context)
326
+ as_yaml(instance, only: only, except: except, context: context),
327
+ **yaml_options
323
328
  )
324
329
  end
325
330
 
@@ -329,14 +334,15 @@ module Shale
329
334
  # @param [Array<Symbol>] only
330
335
  # @param [Array<Symbol>] except
331
336
  # @param [any] context
337
+ # @param [Hash] toml_options
332
338
  #
333
339
  # @return [model instance]
334
340
  #
335
341
  # @api public
336
- def from_toml(toml, only: nil, except: nil, context: nil)
342
+ def from_toml(toml, only: nil, except: nil, context: nil, **toml_options)
337
343
  validate_toml_adapter
338
344
  of_toml(
339
- Shale.toml_adapter.load(toml),
345
+ Shale.toml_adapter.load(toml, **toml_options),
340
346
  only: only,
341
347
  except: except,
342
348
  context: context
@@ -349,14 +355,16 @@ module Shale
349
355
  # @param [Array<Symbol>] only
350
356
  # @param [Array<Symbol>] except
351
357
  # @param [any] context
358
+ # @param [Hash] toml_options
352
359
  #
353
360
  # @return [String]
354
361
  #
355
362
  # @api public
356
- def to_toml(instance, only: nil, except: nil, context: nil)
363
+ def to_toml(instance, only: nil, except: nil, context: nil, **toml_options)
357
364
  validate_toml_adapter
358
365
  Shale.toml_adapter.dump(
359
- as_toml(instance, only: only, except: except, context: context)
366
+ as_toml(instance, only: only, except: except, context: context),
367
+ **toml_options
360
368
  )
361
369
  end
362
370
 
@@ -1000,17 +1008,19 @@ module Shale
1000
1008
  # @param [Array<Symbol>] except
1001
1009
  # @param [any] context
1002
1010
  # @param [true, false] pretty
1011
+ # @param [Hash] json_options
1003
1012
  #
1004
1013
  # @return [String]
1005
1014
  #
1006
1015
  # @api public
1007
- def to_json(only: nil, except: nil, context: nil, pretty: false)
1016
+ def to_json(only: nil, except: nil, context: nil, pretty: false, **json_options)
1008
1017
  self.class.to_json(
1009
1018
  self,
1010
1019
  only: only,
1011
1020
  except: except,
1012
1021
  context: context,
1013
- pretty: pretty
1022
+ pretty: pretty,
1023
+ **json_options
1014
1024
  )
1015
1025
  end
1016
1026
 
@@ -1019,12 +1029,13 @@ module Shale
1019
1029
  # @param [Array<Symbol>] only
1020
1030
  # @param [Array<Symbol>] except
1021
1031
  # @param [any] context
1032
+ # @param [Hash] yaml_options
1022
1033
  #
1023
1034
  # @return [String]
1024
1035
  #
1025
1036
  # @api public
1026
- def to_yaml(only: nil, except: nil, context: nil)
1027
- self.class.to_yaml(self, only: only, except: except, context: context)
1037
+ def to_yaml(only: nil, except: nil, context: nil, **yaml_options)
1038
+ self.class.to_yaml(self, only: only, except: except, context: context, **yaml_options)
1028
1039
  end
1029
1040
 
1030
1041
  # Convert Object to TOML
@@ -1032,12 +1043,13 @@ module Shale
1032
1043
  # @param [Array<Symbol>] only
1033
1044
  # @param [Array<Symbol>] except
1034
1045
  # @param [any] context
1046
+ # @param [Hash] toml_options
1035
1047
  #
1036
1048
  # @return [String]
1037
1049
  #
1038
1050
  # @api public
1039
- def to_toml(only: nil, except: nil, context: nil)
1040
- self.class.to_toml(self, only: only, except: except, context: context)
1051
+ def to_toml(only: nil, except: nil, context: nil, **toml_options)
1052
+ self.class.to_toml(self, only: only, except: except, context: context, **toml_options)
1041
1053
  end
1042
1054
 
1043
1055
  # Convert Object to CSV
@@ -1045,6 +1057,8 @@ module Shale
1045
1057
  # @param [Array<Symbol>] only
1046
1058
  # @param [Array<Symbol>] except
1047
1059
  # @param [any] context
1060
+ # @param [true, false] headers
1061
+ # @param [Hash] csv_options
1048
1062
  #
1049
1063
  # @return [String]
1050
1064
  #
@@ -69,5 +69,7 @@ module Shale
69
69
  value&.iso8601
70
70
  end
71
71
  end
72
+
73
+ register(:date, Date)
72
74
  end
73
75
  end
@@ -25,5 +25,7 @@ module Shale
25
25
  end
26
26
  end
27
27
  end
28
+
29
+ register(:float, Float)
28
30
  end
29
31
  end
@@ -17,5 +17,7 @@ module Shale
17
17
  value&.to_i
18
18
  end
19
19
  end
20
+
21
+ register(:integer, Integer)
20
22
  end
21
23
  end
@@ -17,5 +17,7 @@ module Shale
17
17
  value&.to_s
18
18
  end
19
19
  end
20
+
21
+ register(:string, String)
20
22
  end
21
23
  end
@@ -69,5 +69,7 @@ module Shale
69
69
  value&.iso8601
70
70
  end
71
71
  end
72
+
73
+ register(:time, Time)
72
74
  end
73
75
  end
data/lib/shale/type.rb ADDED
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shale
4
+ module Type
5
+ class << self
6
+ # Register a symbol alias for a Shale::Type::Value class
7
+ #
8
+ # @param [Symbol] type Short type alias
9
+ # @param [Shale::Type::Value] klass Class to register
10
+ #
11
+ # @raise [NotATypeValueError] when klass is not a Shale::Type::Value
12
+ #
13
+ # @example
14
+ # class UnixTimestamp < Shale::Type::Value
15
+ # def self.cast(value)
16
+ # Time.at(value.to_i)
17
+ # end
18
+ # end
19
+ #
20
+ # Shale::Type.register(:unix_timestamp, UnixTimestamp)
21
+ #
22
+ # @api public
23
+ def register(type, klass)
24
+ @registry ||= {}
25
+
26
+ unless klass < Value
27
+ raise NotATypeValueError, "class '#{klass}' is not a valid Shale::Type::Value"
28
+ end
29
+
30
+ @registry[type] = klass
31
+ end
32
+
33
+ # Lookup a Shale::Type::Value class by type alias
34
+ #
35
+ # @param [Symbol] type Type alias
36
+ #
37
+ # @raise [UnknownTypeError] when type is not registered
38
+ #
39
+ # @return [Shale::Type::Value] Class registered for type
40
+ #
41
+ # @example
42
+ #
43
+ # Shale::Type.lookup(:unix_timestamp)
44
+ # # => UnixTimestamp
45
+ #
46
+ # @api public
47
+ def lookup(type)
48
+ klass = @registry[type]
49
+
50
+ raise UnknownTypeError, "unknown type '#{type}'" unless klass
51
+
52
+ klass
53
+ end
54
+ end
55
+ end
56
+ end
data/lib/shale/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Shale
4
4
  # @api private
5
- VERSION = '1.1.0'
5
+ VERSION = '1.2.0'
6
6
  end
data/lib/shale.rb CHANGED
@@ -4,6 +4,7 @@ require 'yaml'
4
4
 
5
5
  require_relative 'shale/mapper'
6
6
  require_relative 'shale/adapter/json'
7
+ require_relative 'shale/type'
7
8
  require_relative 'shale/type/boolean'
8
9
  require_relative 'shale/type/date'
9
10
  require_relative 'shale/type/float'
@@ -80,16 +81,17 @@ module Shale
80
81
  # @api public
81
82
  attr_writer :yaml_adapter
82
83
 
83
- # TOML adapter accessor.
84
+ # TOML adapter accessor. Available adapters are Shale::Adapter::Tomlib
85
+ # and Shale::Adapter::TomRB
84
86
  #
85
- # @param [@see Shale::Adapter::TomlRB] adapter
87
+ # @param [@see Shale::Adapter::Tomlib] adapter
86
88
  #
87
89
  # @example setting adapter
88
- # Shale.toml_adapter = Shale::Adapter::TomlRB
90
+ # Shale.toml_adapter = Shale::Adapter::Tomlib
89
91
  #
90
92
  # @example getting adapter
91
93
  # Shale.toml_adapter
92
- # # => Shale::Adapter::TomlRB
94
+ # # => Shale::Adapter::Tomlib
93
95
  #
94
96
  # @api public
95
97
  attr_accessor :toml_adapter
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shale
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kamil Giszczak
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-17 00:00:00.000000000 Z
11
+ date: 2024-10-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal
@@ -49,6 +49,7 @@ files:
49
49
  - lib/shale/adapter/rexml/document.rb
50
50
  - lib/shale/adapter/rexml/node.rb
51
51
  - lib/shale/adapter/toml_rb.rb
52
+ - lib/shale/adapter/tomlib.rb
52
53
  - lib/shale/attribute.rb
53
54
  - lib/shale/error.rb
54
55
  - lib/shale/mapper.rb
@@ -104,6 +105,7 @@ files:
104
105
  - lib/shale/schema/xml_generator/schema.rb
105
106
  - lib/shale/schema/xml_generator/typed_attribute.rb
106
107
  - lib/shale/schema/xml_generator/typed_element.rb
108
+ - lib/shale/type.rb
107
109
  - lib/shale/type/boolean.rb
108
110
  - lib/shale/type/complex.rb
109
111
  - lib/shale/type/date.rb