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)