oscal 0.1.0 → 0.2.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.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.docker/Dockerfile +19 -0
  3. data/.docker/Makefile +43 -0
  4. data/.docker/docker-compose.yml +14 -0
  5. data/.docker/readme.md +61 -0
  6. data/.github/workflows/rake.yml +15 -0
  7. data/.github/workflows/release.yml +24 -0
  8. data/.gitignore +13 -0
  9. data/.gitmodules +3 -0
  10. data/.hound.yml +5 -0
  11. data/.rspec +0 -1
  12. data/.rubocop.yml +10 -0
  13. data/.ruby-version +1 -0
  14. data/Gemfile +4 -0
  15. data/LICENSE +25 -0
  16. data/Makefile +1 -0
  17. data/README.adoc +66 -0
  18. data/Rakefile +13 -6
  19. data/bin/console +30 -0
  20. data/bin/rspec +27 -0
  21. data/bin/setup +8 -0
  22. data/docker-compose.yml +1 -0
  23. data/exe/convert2oscalyaml.rb +560 -0
  24. data/lib/oscal/add.rb +26 -0
  25. data/lib/oscal/address.rb +22 -0
  26. data/lib/oscal/address_line.rb +11 -0
  27. data/lib/oscal/alter.rb +22 -0
  28. data/lib/oscal/assembly.rb +119 -0
  29. data/lib/oscal/assessment_plan.rb +28 -0
  30. data/lib/oscal/assessment_result.rb +230 -0
  31. data/lib/oscal/attribute_type_hash.rb +80 -0
  32. data/lib/oscal/back_matter.rb +20 -0
  33. data/lib/oscal/base64_object.rb +11 -0
  34. data/lib/oscal/base_class.rb +50 -0
  35. data/lib/oscal/catalog.rb +51 -10
  36. data/lib/oscal/choice.rb +11 -0
  37. data/lib/oscal/citation.rb +22 -0
  38. data/lib/oscal/combine.rb +11 -0
  39. data/lib/oscal/common_utils.rb +35 -0
  40. data/lib/oscal/constraint.rb +20 -0
  41. data/lib/oscal/control.rb +20 -30
  42. data/lib/oscal/custom.rb +22 -0
  43. data/lib/oscal/datatypes.rb +50 -0
  44. data/lib/oscal/document_id.rb +11 -0
  45. data/lib/oscal/email_address.rb +11 -0
  46. data/lib/oscal/exclude_control.rb +22 -0
  47. data/lib/oscal/external_id.rb +11 -0
  48. data/lib/oscal/group.rb +26 -34
  49. data/lib/oscal/guideline.rb +11 -0
  50. data/lib/oscal/hash_object.rb +11 -0
  51. data/lib/oscal/import_object.rb +22 -0
  52. data/lib/oscal/include_control.rb +22 -0
  53. data/lib/oscal/insert_control.rb +22 -0
  54. data/lib/oscal/link.rb +11 -0
  55. data/lib/oscal/list.rb +160 -0
  56. data/lib/oscal/location.rb +31 -0
  57. data/lib/oscal/location_uuid.rb +11 -0
  58. data/lib/oscal/logger.rb +8 -0
  59. data/lib/oscal/matching.rb +11 -0
  60. data/lib/oscal/member_of_organization.rb +11 -0
  61. data/lib/oscal/merge.rb +20 -0
  62. data/lib/oscal/metadata_block.rb +28 -13
  63. data/lib/oscal/modify.rb +22 -0
  64. data/lib/oscal/parameter.rb +22 -19
  65. data/lib/oscal/parsing_functions.rb +19 -0
  66. data/lib/oscal/part.rb +14 -22
  67. data/lib/oscal/party.rb +36 -0
  68. data/lib/oscal/party_uuid.rb +11 -0
  69. data/lib/oscal/profile.rb +33 -7
  70. data/lib/oscal/property.rb +4 -25
  71. data/lib/oscal/remove.rb +11 -0
  72. data/lib/oscal/resource.rb +29 -0
  73. data/lib/oscal/responsible_party.rb +24 -0
  74. data/lib/oscal/revision.rb +23 -0
  75. data/lib/oscal/rlink.rb +20 -0
  76. data/lib/oscal/role.rb +22 -0
  77. data/lib/oscal/select.rb +20 -0
  78. data/lib/oscal/serializer.rb +17 -4
  79. data/lib/oscal/set_parameter.rb +31 -0
  80. data/lib/oscal/telephone_number.rb +11 -0
  81. data/lib/oscal/test.rb +11 -0
  82. data/lib/oscal/url.rb +11 -0
  83. data/lib/oscal/value.rb +37 -0
  84. data/lib/oscal/version.rb +1 -1
  85. data/lib/oscal/with_id.rb +40 -0
  86. data/lib/oscal.rb +1 -13
  87. data/oscal.gemspec +9 -11
  88. data/spec/oscal/catalog_spec.rb +40 -0
  89. data/spec/oscal_spec.rb +7 -0
  90. data/spec/sample_inputs/import-ap.json +4 -0
  91. data/spec/spec_helper.rb +15 -0
  92. metadata +84 -10
  93. data/lib/oscal/component.rb +0 -14
  94. data/lib/oscal/prose.rb +0 -13
  95. data/lib/oscal/statement.rb +0 -12
@@ -1,21 +1,36 @@
1
- module Oscal
2
- class MetadataBlock
1
+ require_relative "base_class"
3
2
 
4
- METADATA_VALUES = %i(title published last_modified version
5
- oscal_version remarks)
3
+ module Oscal
4
+ class MetadataBlock < Oscal::BaseClass
5
+ KEY = %i(title published last_modified version
6
+ oscal_version revisions document_ids props links roles
7
+ locations parties responsible_parties remarks)
6
8
 
7
- attr_accessor *METADATA_VALUES
9
+ attr_accessor *KEY
8
10
 
9
- def initialize(options={})
10
- options.each_pair.each do |key,val|
11
- key_name = key.gsub('-','_')
11
+ attr_serializable *KEY
12
12
 
13
- unless METADATA_VALUES.include?(key_name.to_sym)
14
- raise UnknownAttributeError.new("Unknown key `#{key}` in MetadataBlock")
15
- end
16
- self.send("#{key_name}=", val)
13
+ def set_value(key_name, val)
14
+ case key_name
15
+ when "revisions"
16
+ Revision.wrap(val)
17
+ when "docuement_ids"
18
+ DocumentId.wrap(val)
19
+ when "props"
20
+ Property.wrap(val)
21
+ when "links"
22
+ Link.wrap(val)
23
+ when "roles"
24
+ Role.wrap(val)
25
+ when "locations"
26
+ Location.wrap(val)
27
+ when "parties"
28
+ Party.wrap(val)
29
+ when "responsible_parties"
30
+ ResponsibleParty.wrap(val)
31
+ else
32
+ val
17
33
  end
18
34
  end
19
-
20
35
  end
21
36
  end
@@ -0,0 +1,22 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Modify < Oscal::BaseClass
5
+ KEY = %i(set_parameters alters)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+
11
+ def set_value(key_name, val)
12
+ case key_name
13
+ when "set_parameters"
14
+ SetParameter.wrap(val)
15
+ when "alters"
16
+ Alter.wrap(val)
17
+ else
18
+ val
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,27 +1,30 @@
1
+ require_relative "base_class"
2
+
1
3
  module Oscal
2
- class Parameter
4
+ class Parameter < Oscal::BaseClass
5
+ KEY = %i(id klass depneds_on props links label usage
6
+ constraints guidelines values select remarks)
3
7
 
4
- KEY = %i(id label select)
5
8
  attr_accessor *KEY
6
9
 
7
- def self.wrap(obj)
8
- return obj if obj.is_a? Parameter
9
- return Parameter.new(obj) unless obj.is_a? Array
10
-
11
- obj.map do |x|
12
- Parameter.wrap(x)
13
- end
14
- end
15
-
16
- def initialize(options={})
17
- options.each_pair.each do |key,val|
18
- key_name = key.gsub('-','_')
19
-
20
- unless KEY.include?(key_name.to_sym)
21
- raise UnknownAttributeError.new("Unknown key `#{key}` in Parameter")
22
- end
10
+ attr_serializable *KEY
23
11
 
24
- self.send("#{key_name}=", val)
12
+ def set_value(key_name, val)
13
+ case key_name
14
+ when "props"
15
+ Property.wrap(val)
16
+ when "links"
17
+ Link.wrap(val)
18
+ when "constraints"
19
+ Constraint.wrap(val)
20
+ when "guidelines"
21
+ Guideline.wrap(val)
22
+ when "values"
23
+ Value.wrap(val)
24
+ when "select"
25
+ Select.wrap(val)
26
+ else
27
+ val
25
28
  end
26
29
  end
27
30
  end
@@ -0,0 +1,19 @@
1
+ module Oscal
2
+ module ParsingFunctions
3
+ def sym2str(key)
4
+ if key.is_a?(Symbol)
5
+ key.to_s.gsub("-", "_").gsub("class", "klass")
6
+ elsif key.is_a?(String)
7
+ key
8
+ end
9
+ end
10
+
11
+ def str2sym(var)
12
+ if var.is_a?(String)
13
+ var.gsub("-", "_").gsub("class", "klass").to_sym
14
+ elsif var.is_a?(Symbol)
15
+ var
16
+ end
17
+ end
18
+ end
19
+ end
data/lib/oscal/part.rb CHANGED
@@ -1,31 +1,23 @@
1
- require_relative "serializer"
1
+ require_relative "base_class"
2
2
 
3
3
  module Oscal
4
- class Part
5
- include Serializer
4
+ class Part < Oscal::BaseClass
5
+ KEY = %i(id name ns klass title props prose parts links)
6
6
 
7
- KEY = %i(id name prose)
8
7
  attr_accessor *KEY
9
- attr_serializable *KEY
10
-
11
- def self.wrap(obj)
12
- return obj if obj.is_a? Part
13
- return Part.new(obj) unless obj.is_a? Array
14
8
 
15
- obj.map do |x|
16
- Part.wrap(x)
17
- end
18
- end
19
-
20
- def initialize(options={})
21
- options.each_pair.each do |key,val|
22
- key_name = key.gsub('-','_')
23
-
24
- unless KEY.include?(key_name.to_sym)
25
- raise UnknownAttributeError.new("Unknown key `#{key}` in Part")
26
- end
9
+ attr_serializable *KEY
27
10
 
28
- self.send("#{key_name}=", val)
11
+ def set_value(key_name, val)
12
+ case key_name
13
+ when "props"
14
+ Property.wrap(val)
15
+ when "parts"
16
+ Part.wrap(val)
17
+ when "links"
18
+ Link.wrap(val)
19
+ else
20
+ val
29
21
  end
30
22
  end
31
23
  end
@@ -0,0 +1,36 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Party < Oscal::BaseClass
5
+ KEY = %i(uuid type name short_name external_ids props links
6
+ email_addresses telephone_numbers addresses location_uuids
7
+ member_of_organizations remakrs)
8
+
9
+ attr_accessor *KEY
10
+
11
+ attr_serializable *KEY
12
+
13
+ def set_value(key_name, val)
14
+ case key_name
15
+ when "external_ids"
16
+ ExternalId.wrap(val)
17
+ when "props"
18
+ Property.wrap(val)
19
+ when "links"
20
+ Link.wrap(val)
21
+ when "email_addresses"
22
+ EmailAddress.wrap(val)
23
+ when "telephone_numbers"
24
+ TelephoneNumber.wrap(val)
25
+ when "addresses"
26
+ Address.wrap(val)
27
+ when "location_uuids"
28
+ LocationUuid.wrap(val)
29
+ when "member_of_organizations"
30
+ MemberOfOrganization.wrap(val)
31
+ else
32
+ val
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,11 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class PartyUuid < Oscal::BaseClass
5
+ KEY = %i(val)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+ end
11
+ end
data/lib/oscal/profile.rb CHANGED
@@ -1,11 +1,37 @@
1
+ require_relative "serializer"
2
+ require_relative "common_utils"
3
+
1
4
  module Oscal
2
- class Profile
3
- attr_accessor :metadata, :imports, :controls
5
+ class Profile
6
+ include Serializer
7
+ include CommonUtils
8
+
9
+ KEY = %i(uuid metadata imports merge modify back_matter)
10
+ attr_accessor *KEY
11
+
12
+ attr_serializable *KEY
13
+
14
+ def initialize(uuid, metadata, imports, merge, modify, back_matter)
15
+ @uuid = uuid
16
+ @metadata = MetadataBlock.new(metadata)
17
+ @imports = ImportObject.wrap(imports) if imports
18
+ @merge = Merge.wrap(merge) if merge
19
+ @modify = Modify.wrap(modify) if modify
20
+ @back_matter = BackMatter.wrap(back_matter) if back_matter
21
+ end
22
+
23
+ def self.load_from_yaml(path)
24
+ yaml_data = safe_load_yaml(path)
25
+ yaml_profile = yaml_data["profile"]
26
+
27
+ uuid = yaml_profile["uuid"]
28
+ metadata = yaml_profile["metadata"]
29
+ imports = yaml_profile["imports"]
30
+ merge = yaml_profile["merge"]
31
+ modify = yaml_profile["modify"]
32
+ back_matter = yaml_profile["back-matter"]
4
33
 
5
- def initialize(metadata, imports, controls)
6
- @metadata = metadata
7
- @imports = imports
8
- @controls = controls
34
+ Profile.new(uuid, metadata, imports, merge, modify, back_matter)
9
35
  end
10
36
  end
11
- end
37
+ end
@@ -1,32 +1,11 @@
1
- require_relative "serializer"
1
+ require_relative "base_class"
2
2
 
3
3
  module Oscal
4
- class Property
5
- include Serializer
4
+ class Property < Oscal::BaseClass
5
+ KEY = %i(name uuid ns value klass remarks)
6
6
 
7
- KEY = %i(name value)
8
7
  attr_accessor *KEY
9
- attr_serializable *KEY
10
-
11
- def self.wrap(obj)
12
- return obj if obj.is_a? Property
13
- return Property.new(obj) unless obj.is_a? Array
14
-
15
- obj.map do |x|
16
- Property.wrap(x)
17
- end
18
- end
19
8
 
20
- def initialize(options={})
21
- options.each_pair.each do |key,val|
22
- key_name = key.gsub('-','_')
23
-
24
- unless KEY.include?(key_name.to_sym)
25
- raise UnknownAttributeError.new("Unknown key `#{key}` in Property")
26
- end
27
-
28
- self.send("#{key_name}=", val)
29
- end
30
- end
9
+ attr_serializable *KEY
31
10
  end
32
11
  end
@@ -0,0 +1,11 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Remove < Oscal::BaseClass
5
+ KEY = %i(by_name by_class by_id by_item_name by_ns)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+ end
11
+ end
@@ -0,0 +1,29 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Resource < Oscal::BaseClass
5
+ KEY = %i(uuid title description props docuement_ids citation rlinks
6
+ base64 remarks)
7
+
8
+ attr_accessor *KEY
9
+
10
+ attr_serializable *KEY
11
+
12
+ def set_value(key_name, val)
13
+ case key_name
14
+ when "props"
15
+ Property.wrap(val)
16
+ when "document_ids"
17
+ DocumentId.wrap(val)
18
+ when "citation"
19
+ Citation.wrap(val)
20
+ when "rlinks"
21
+ Rlink.wrap(val)
22
+ when "base64"
23
+ Base64Object.wrap(val)
24
+ else
25
+ val
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class ResponsibleParty < Oscal::BaseClass
5
+ KEY = %i(role_id party_uuids props links remakrs)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+
11
+ def set_value(key_name, val)
12
+ case key_name
13
+ when "party_uuids"
14
+ PartyUuid.wrap(val)
15
+ when "props"
16
+ Property.wrap(val)
17
+ when "links"
18
+ Link.wrap(val)
19
+ else
20
+ val
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Revision < Oscal::BaseClass
5
+ KEY = %i(title published last_modified version oscal_version
6
+ props links remarks)
7
+
8
+ attr_accessor *KEY
9
+
10
+ attr_serializable *KEY
11
+
12
+ def set_value(key_name, val)
13
+ case key_name
14
+ when "props"
15
+ Property.wrap(val)
16
+ when "links"
17
+ Link.wrap(val)
18
+ else
19
+ val
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Rlink < Oscal::BaseClass
5
+ KEY = %i(href media_type hashes)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+
11
+ def set_value(key_name, val)
12
+ case key_name
13
+ when "hashes"
14
+ HashObject.wrap(val)
15
+ else
16
+ val
17
+ end
18
+ end
19
+ end
20
+ end
data/lib/oscal/role.rb ADDED
@@ -0,0 +1,22 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Role < Oscal::BaseClass
5
+ KEY = %i(id title short_name description props links remakrs)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+
11
+ def set_value(key_name, val)
12
+ case key_name
13
+ when "props"
14
+ Property.wrap(val)
15
+ when "links"
16
+ Link.wrap(val)
17
+ else
18
+ val
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Select < Oscal::BaseClass
5
+ KEY = %i(how_many choice)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+
11
+ def set_value(key_name, val)
12
+ case key_name
13
+ when "choice"
14
+ Choice.wrap(val)
15
+ else
16
+ val
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,10 +1,10 @@
1
- require 'yaml'
1
+ require "yaml"
2
2
 
3
3
  module Oscal
4
4
  module Serializer
5
5
  def to_h
6
6
  instance_variables.each_with_object({}) do |var, hash|
7
- var_name = var.to_s.delete('@')
7
+ var_name = var.to_s.delete("@")
8
8
  hash[var_name] = instance_variable_get(var)
9
9
  end
10
10
  end
@@ -36,7 +36,7 @@ module Oscal
36
36
  end
37
37
 
38
38
  def from_yaml(yaml_string)
39
- data = YAML.load(yaml_string)
39
+ data = YAML.safe_load(yaml_string)
40
40
  from_h(data)
41
41
  end
42
42
 
@@ -44,6 +44,17 @@ module Oscal
44
44
  raise NotImplementedError, "#{self}#from_xml not implemented!"
45
45
  end
46
46
 
47
+ # Psych >= 4 requires permitted_classes to load such classes
48
+ # https://github.com/ruby/psych/issues/533
49
+ def safe_load_yaml(path)
50
+ YAML.load_file(
51
+ path,
52
+ permitted_classes: [::Time, ::Date, ::DateTime],
53
+ )
54
+ rescue ArgumentError
55
+ YAML.load_file(path)
56
+ end
57
+
47
58
  private
48
59
 
49
60
  def attribute_names
@@ -54,7 +65,9 @@ module Oscal
54
65
  attrs.each do |attr|
55
66
  attribute_names << attr.to_sym
56
67
  define_method(attr) { instance_variable_get("@#{attr}") }
57
- define_method("#{attr}=") { |value| instance_variable_set("@#{attr}", value) }
68
+ define_method("#{attr}=") do |value|
69
+ instance_variable_set("@#{attr}", value)
70
+ end
58
71
  end
59
72
  end
60
73
  end
@@ -0,0 +1,31 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class SetParameter < Oscal::BaseClass
5
+ KEY = %i(param_id klass depneds_on props links label usage
6
+ constraints guidelines values select)
7
+
8
+ attr_accessor *KEY
9
+
10
+ attr_serializable *KEY
11
+
12
+ def set_value(key_name, val)
13
+ case key_name
14
+ when "props"
15
+ Property.wrap(val)
16
+ when "links"
17
+ Link.wrap(val)
18
+ when "constraints"
19
+ Constraint.wrap(val)
20
+ when "guidelines"
21
+ Guideline.wrap(val)
22
+ when "values"
23
+ Value.wrap(val)
24
+ when "select"
25
+ Select.wrap(val)
26
+ else
27
+ val
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class TelephoneNumber < Oscal::BaseClass
5
+ KEY = %i(type number)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+ end
11
+ end
data/lib/oscal/test.rb ADDED
@@ -0,0 +1,11 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Test < Oscal::BaseClass
5
+ KEY = %i(expression remarks)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+ end
11
+ end
data/lib/oscal/url.rb ADDED
@@ -0,0 +1,11 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Url < Oscal::BaseClass
5
+ KEY = %i(val)
6
+
7
+ attr_accessor *KEY
8
+
9
+ attr_serializable *KEY
10
+ end
11
+ end
@@ -0,0 +1,37 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class Value < Oscal::BaseClass
5
+ include Serializer
6
+
7
+ KEY = %i(val)
8
+ attr_accessor *KEY
9
+
10
+ attr_serializable *KEY
11
+
12
+ def self.wrap(obj)
13
+ return obj if obj.is_a? Value
14
+ return Value.new(obj) unless obj.is_a? Array
15
+
16
+ obj.map do |x|
17
+ Value.wrap(x)
18
+ end
19
+ end
20
+
21
+ def initialize(options = {})
22
+ unless options.is_a? Hash
23
+ options = { "val" => options }
24
+ end
25
+
26
+ options.each_pair.each do |key, val|
27
+ key_name = key.gsub("-", "_")
28
+
29
+ unless KEY.include?(key_name.to_sym)
30
+ raise UnknownAttributeError.new("Unknown key `#{key}` in Value")
31
+ end
32
+
33
+ self.send("#{key_name}=", val)
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/oscal/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Oscal
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,40 @@
1
+ require_relative "base_class"
2
+
3
+ module Oscal
4
+ class WithId < Oscal::BaseClass
5
+ include Serializer
6
+
7
+ KEY = %i(val)
8
+
9
+ attr_accessor *KEY
10
+
11
+ attr_serializable *KEY
12
+
13
+ def self.wrap(obj)
14
+ return obj if obj.is_a? WithId
15
+ return WithId.new(obj) unless obj.is_a? Array
16
+
17
+ obj.map do |x|
18
+ WithId.wrap(x)
19
+ end
20
+ end
21
+
22
+ def initialize(options = {})
23
+ klass = self.class
24
+
25
+ unless options.is_a? Hash
26
+ options = { KEY.first.to_s => options }
27
+ end
28
+
29
+ options.each_pair.each do |key, val|
30
+ key_name = key.gsub("-", "_")
31
+
32
+ unless KEY.include?(key_name.to_sym)
33
+ raise UnknownAttributeError.new("Unknown key `#{key}` in #{klass.name}")
34
+ end
35
+
36
+ send("#{key_name}=", val)
37
+ end
38
+ end
39
+ end
40
+ end