lib/jamf/api/abstract_classes/json_object.rb in ruby-jss-1.4.1 vs lib/jamf/api/abstract_classes/json_object.rb in ruby-jss-1.5.1
- old
+ new
@@ -177,29 +177,42 @@
# - readonly:
# - multi:
# - enum:
# - validator:
# - aliases:
+ # - filter_key:
#
# For an example of an OBJECT_MODEL hash, see {Jamf::MobileDeviceDetails::OBJECT_MODEL}
#
# The details for each key's value are as follows. Note that omitting a
# boolean key is the same as setting it to false.
#
# class: \[Symbol or Class]
# -----------------
# This is the only required key for all attributes.
#
- # Symbol is one of :string, :integer, :float, or :boolean
- # These are the JSON data types that don't need parsing into ruby
+ # ---
+ # Symbol is one of :string, :integer, :float, :boolean, or :j_id
+ #
+ # The first four are the JSON data types that don't need parsing into ruby
# beyond that done by `JSON.parse`. When processing an attribute with one of
# these symbols as the `class:`, the JSON value is used as-is.
#
- # When this is not a Symbol, it must be an actual class, such as
+ # The ':j_id' symbol means this value is an id used to reference an object in
+ # a collection resource of the API - all such objects have an 'id' attribute
+ # which is a String containing an Integer.
+ #
+ # These ids are used not only as the id attribute of the object itself, but
+ # if an object contains references to one or more other objects, those
+ # references are also ':j_id' values.
+ # In setters and .create, :j_id values can take either an integer or an
+ # integer-in-a-string, and are stored as integer-in-a-string/
+ #
+ # When 'class:' is not a Symbol, it must be an actual class, such as
# Jamf::Timestamp or Jamf::PurchasingData.
#
- # Classes used this way _must_:
+ # Actual classes used this way _must_:
#
# - Have an #initialize method that takes two parameters and performs
# validation on them:
#
# A first positional parameter, the value used to create the instance,
@@ -278,14 +291,15 @@
#
# 1. so ruby-jss knows when changes are made and need to be saved
# 2. so that validation can be performed on values added to the array.
#
#
- # enum: \[Constant -> Array<Constants> ]
+ # enum: \[Constant -> Array ]
# -----------------
# This is a constant defined somewhere in the Jamf module. The constant
- # must contain an Array of other Constant values, usually Strings.
+ # must contain an Array of values, usually Strings. You may or may not choose
+ # to define the array members as constants themselves.
#
# Example:
# > Attribute `:type` has enum: Jamf::ExtentionAttribute::DATA_TYPES
# >
# > The constant Jamf::ExtentionAttribute::DATA_TYPES is defined thus:
@@ -303,13 +317,14 @@
# > When setting the type attribute via `#type = newval`,
# > `Jamf::ExtentionAttribute::DATA_TYPES.include? newval` must be true
# >
#
# Setters for attributes with an enum require that the new value is
- # a member of the array as seen above. When using such setters, its wise to
- # use the array members themselves rather than a different but identical string,
- # however either will work. In other words, this:
+ # a member of the array as seen above. When using such setters, If you defined
+ # the array members as constants themselves, it is wise to use those rather
+ # than a different but identical string, however either will work.
+ # In other words, this:
#
# my_ea.dataType = Jamf::ExtentionAttribute::DATA_TYPE_INTEGER
#
# is preferred over:
#
@@ -354,10 +369,18 @@
# Other names for this attribute. If provided, getters, and setters will
# be made for all aliases. Should be used very sparingly.
#
# Attributes of class :boolean automatically have a getter alias ending with a '?'.
#
+ # filter_key: \[Boolean]
+ # -----------------
+ # For subclasses of CollectionResource, GETting the main endpoint will return
+ # the entire collection. Some of these endpoints support RSQL filters to return
+ # only those objects that match the filter. If this attribute can be used as
+ # a field for filtering, set filter_key: to true, and filters will be used
+ # where possible to optimize GET requests.
+ #
# Documenting your code
# ---------------------
# For documenting attributes with YARD, put this above each
# attribute name key:
#
@@ -399,27 +422,40 @@
# Attributes that are not readonly are subject to data validation when values are
# assigned. How that validation happens depends on the definition of the
# attribute as described above. Validation failure will raise an exception,
# usually Jamf::InvalidDataError.
#
- # If the attribute is defined with an enum, the value must be
- # a key or value of the enum.
+ # Only one value-validation is applied, depending on the attribute definition:
#
- # If the attribute's class: is defined as a Class, (e.g. Jamf::Timestamp)
- # its .new method is called with the value and the current API connection.
- # The class itself performs valuation when the value is used to
- # instantiate it.
+ # - If the attribute is defined with a specific validator, the value is passed
+ # to that validator, and other validators are ignored
#
- # If the attribute is defined with a validator, the value is passed
- # to that validator.
+ # - If the attribute is defined with an enum, the value must be
+ # a value of the enum.
#
- # If the attribute is defined as a :string, :integer, :float or :bool
- # without an enum or validator, it is checked to be the correct type
+ # - If the attribute is defined as a :string, :integer, :float or :bool
+ # without an enum or validator, it is confirmed to be the correct type
#
- # If an attribute is an identifier, it must be unique in its class and
- # API connection.
+ # - If the attribute is defined to hold a :j_id, the Validate.j_id method
+ # is used, it must be an integer or integer-in-string
#
+ # - If the attribute is defined to hold a JAMF class, (e.g. Jamf::Timestamp)
+ # the class itself performs validation on the value when instantiated
+ # with the value.
+ #
+ # - Otherwise, the value is used unchanged with no validation
+ #
+ # Additionally:
+ #
+ # - If an attribute is an identifier, it must be unique in its class and
+ # API connection.
+ #
+ # - If an attribute is required, it may not be nil or empty
+ #
+ # - If an attribute is :multi, the value must be an array and each member
+ # value is validated individually
+ #
# ### Constructor / Instantiation {#constructor}
#
# The .new method should rarely (never?) be called directly for any JSONObject
# class.
#
@@ -476,16 +512,10 @@
#####################################
# These classes are used from JSON in the raw
JSON_TYPE_CLASSES = %i[string integer float boolean].freeze
- # Predicate (boolean) attibutes that start with this RE will
- # have an alias without the 'is' so :isManaged will have
- # getters isManaged? and managed?
- #
- PREDICATE_RE = /^is([A-Z]\w+)$/.freeze
-
# Public Class Methods
#####################################
# By default, JSONObjects (as a whole) are mutable,
# although some attributes may not be (see OBJECT_MODEL in the JSONObject
@@ -540,12 +570,14 @@
got_primary = false
need_list_methods = ancestors.include?(Jamf::CollectionResource)
self::OBJECT_MODEL.each do |attr_name, attr_def|
- create_list_methods(attr_name, attr_def) if need_list_methods && attr_def[:identifier]
+ # don't make one for :id, that one's hard-coded into CollectionResource
+ create_list_methods(attr_name, attr_def) if need_list_methods && attr_def[:identifier] && attr_name != :id
+
# there can be only one (primary ident)
if attr_def[:identifier] == :primary
raise Jamf::UnsupportedError, 'Two identifiers marked as :primary' if got_primary
got_primary = true
@@ -589,13 +621,10 @@
# create the default aliases for booleans
##############################
def self.define_predicates(attr_name)
alias_method("#{attr_name}?", attr_name)
- return unless attr_name.to_s =~ PREDICATE_RE
-
- alias_method("#{Regexp.last_match(1).downcase}?", attr_name)
end
# create setter(s) for an attribute, and any aliases needed
##############################
def self.create_setters(attr_name, attr_def)
@@ -608,10 +637,11 @@
# single value
define_method("#{attr_name}=") do |new_value|
new_value = validate_attr attr_name, new_value
old_value = instance_variable_get("@#{attr_name}")
return if new_value == old_value
+
instance_variable_set("@#{attr_name}", new_value)
note_unsaved_change attr_name, old_value
end # define method
return unless attr_def[:aliases]
@@ -636,13 +666,15 @@
##############################
def self.create_full_array_setters(attr_name, attr_def)
define_method("#{attr_name}=") do |new_value|
instance_variable_set("@#{attr_name}", []) unless instance_variable_get("@#{attr_name}").is_a?(Array)
raise Jamf::InvalidDataError, 'Value must be an Array' unless new_value.is_a? Array
+
new_value.map! { |item| validate_attr attr_name, item }
old_value = instance_variable_get("@#{attr_name}")
return if new_value == old_value
+
instance_variable_set("@#{attr_name}", new_value)
note_unsaved_change attr_name, old_value
end # define method
return unless attr_def[:aliases]
@@ -750,30 +782,10 @@
# Used by auto-generated setters and .create to validate new values.
#
# returns a valid value or raises an exception
#
- # If the attribute is defined to hold a JAMF class, (e.g. Jamf::Timestamp)
- # the class itself performs validation on the value when instantiated
- # with the value.
- #
- # If the attribute is defined with an enum, the value must be
- # a key of the enum.
- #
- # If the attribute is defined with a validator, the value is passed
- # to that validator.
- #
- # If the attribute is defined as a :string, :integer, :float or :bool
- # without an enum or validator, it is confirmed to be the correct type
- #
- # If the attribute is required, it can't be nil or empty
- #
- # If the attribute is an identifier, and the class is a subclass of
- # CollectionResource, it must be unique among the collection
- #
- # Otherwise, the value is returned unchanged.
- #
# This method only validates single values. When called from multi-value
# setters, it is used for each value individually.
#
# @param attr_name[Symbol], a top-level key from OBJECT_MODEL for this class
#
@@ -781,41 +793,16 @@
#
# @return [Object] The validated, possibly converted, value.
#
def self.validate_attr(attr_name, value, cnx: Jamf.cnx)
attr_def = self::OBJECT_MODEL[attr_name]
-
raise ArgumentError, "Unknown attribute: #{attr_name} for #{self} objects" unless attr_def
# validate our value, which will raise an error or
# convert the value to the required type.
- value =
+ value = validate_attr_value(attr_def, value, cnx: Jamf.cnx)
- # by enum, must be a value of the enum
- if attr_def[:enum]
- return value if attr_def[:enum].include? value
- raise Jamf::InvalidDataError, "Value must be one of: :#{attr_def[:enum].join ', :'}"
-
- # by class, the class validates the value passed with .new
- elsif attr_def[:class].is_a? Class
- klass = attr_def[:class]
- # validation happens in klass.new
- value.is_a?(klass) ? value : klass.new(value, cnx: cnx)
-
- # by specified Validate method - pass to the method
- elsif attr_def[:validator]
- Jamf::Validate.send(attr_def[:validator], value)
-
- # By json primative type - pass to the matching validate method
- elsif JSON_TYPE_CLASSES.include? attr_def[:class]
- Jamf::Validate.send(attr_def[:class], value)
-
- # raw, no validation, should be rare
- else
- value
- end # if
-
# if this is required, it can't be nil or empty
if attr_def[:required]
raise Jamf::MissingDataError, "Required attribute '#{attr_name}:' may not be nil or empty" if value.to_s.empty?
end
@@ -824,10 +811,45 @@
value
end # validate_attr(attr_name, value)
private_class_method :validate_attr
+ # Validate an attribute value itself, as part of validating the attribute
+ # as a whole. Only one validation is applied, which one is
+ # determined in the order described in the #### Data Validation section
+ # of the JSONObject class comments
+ #
+ # See .validate_attr, which calls this
+ def self.validate_attr_value(attr_def, value, cnx: Jamf.cnx)
+ # by specified Validate method
+ if attr_def[:validator]
+ Jamf::Validate.send attr_def[:validator], value
+
+ # by enum, must be a value of the enum
+ elsif attr_def[:enum]
+ Jamf::Validate.in_enum(value, attr_def[:enum])
+
+ # By json primative type - pass to the matching validate method
+ elsif JSON_TYPE_CLASSES.include? attr_def[:class]
+ Jamf::Validate.send attr_def[:class], value
+
+ # a JPAPI id?
+ elsif attr_def[:class] == :j_id
+ Jamf::Validate.j_id value
+
+ # by Class, the class validates the value passed with .new
+ elsif attr_def[:class].is_a? Class
+ klass = attr_def[:class]
+ value.is_a?(klass) ? value : klass.new(value, cnx: cnx)
+
+ # raw, no validation, should be rare
+ else
+ value
+ end # if
+ end
+ private_class_method :validate_attr_value
+
# Attributes
#####################################
# @return [Jamf::Connection] the API connection thru which we deal with
# this objcet.
@@ -1044,10 +1066,14 @@
# a Class value
elsif attr_def[:class].class == Class
attr_def[:class].new api_value, cnx: @cnx
+ # a :j_id value. See the docs for OBJECT_MODEL in Jamf::JSONObject
+ elsif attr_def[:class] == :j_id
+ api_value.to_s
+
# a JSON value
else
api_value
end # if attr_def[:class].class
end
@@ -1056,14 +1082,12 @@
#
# @param (see parse_single_init_value)
# @return (see parse_single_init_value)
#
def parse_enum_value(api_value, attr_name, attr_def)
- if attr_def[:enum].include? api_value
- api_value
- else
- raise Jamf::InvalidDataError, "#{api_value} is not in the enum for attribute #{attr_name}"
- end
+ raise Jamf::InvalidDataError, "#{api_value} is not in the enum for attribute #{attr_name}" unless attr_def[:enum].include? api_value
+
+ api_value
end
# call to_jamf on a single value
#
def single_to_jamf(raw_value, attr_def)