windoo 1.0.1

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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +9 -0
  3. data/LICENSE.txt +177 -0
  4. data/README.md +222 -0
  5. data/lib/windoo/base_classes/array_manager.rb +335 -0
  6. data/lib/windoo/base_classes/criteria_manager.rb +327 -0
  7. data/lib/windoo/base_classes/criterion.rb +226 -0
  8. data/lib/windoo/base_classes/json_object.rb +472 -0
  9. data/lib/windoo/configuration.rb +221 -0
  10. data/lib/windoo/connection/actions.rb +152 -0
  11. data/lib/windoo/connection/attributes.rb +156 -0
  12. data/lib/windoo/connection/connect.rb +402 -0
  13. data/lib/windoo/connection/constants.rb +55 -0
  14. data/lib/windoo/connection/token.rb +489 -0
  15. data/lib/windoo/connection.rb +92 -0
  16. data/lib/windoo/converters.rb +31 -0
  17. data/lib/windoo/exceptions.rb +34 -0
  18. data/lib/windoo/mixins/api_collection.rb +408 -0
  19. data/lib/windoo/mixins/constants.rb +43 -0
  20. data/lib/windoo/mixins/default_connection.rb +75 -0
  21. data/lib/windoo/mixins/immutable.rb +34 -0
  22. data/lib/windoo/mixins/loading.rb +38 -0
  23. data/lib/windoo/mixins/patch/component.rb +102 -0
  24. data/lib/windoo/mixins/software_title/extension_attribute.rb +106 -0
  25. data/lib/windoo/mixins/utility.rb +23 -0
  26. data/lib/windoo/objects/capability.rb +82 -0
  27. data/lib/windoo/objects/capability_manager.rb +52 -0
  28. data/lib/windoo/objects/component.rb +99 -0
  29. data/lib/windoo/objects/component_criteria_manager.rb +26 -0
  30. data/lib/windoo/objects/component_criterion.rb +66 -0
  31. data/lib/windoo/objects/extension_attribute.rb +149 -0
  32. data/lib/windoo/objects/kill_app.rb +92 -0
  33. data/lib/windoo/objects/kill_app_manager.rb +89 -0
  34. data/lib/windoo/objects/patch.rb +235 -0
  35. data/lib/windoo/objects/patch_manager.rb +240 -0
  36. data/lib/windoo/objects/requirement.rb +85 -0
  37. data/lib/windoo/objects/requirement_manager.rb +52 -0
  38. data/lib/windoo/objects/software_title.rb +407 -0
  39. data/lib/windoo/validate.rb +548 -0
  40. data/lib/windoo/version.rb +15 -0
  41. data/lib/windoo/zeitwerk_config.rb +158 -0
  42. data/lib/windoo.rb +56 -0
  43. metadata +141 -0
@@ -0,0 +1,548 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ module Windoo
10
+
11
+ # A collection of methods implementing data constraints
12
+ #
13
+ # Some of these methods can take multiple input types, such as a String
14
+ # or an number. All of them will either raise an exception
15
+ # if the value isn't valid, or will return a standardized form of the input
16
+ # (e.g. a number or a Time, even if given a String)
17
+ #
18
+ module Validate
19
+
20
+ # Thes methods all raise this error
21
+ def self.raise_invalid_data_error(msg)
22
+ raise Windoo::InvalidDataError, msg
23
+ end
24
+
25
+ # Validate that a value is valid based on its
26
+ # definition in an objects OAPI_PROPERTIES constant.
27
+ #
28
+ # @param val [Object] The value to validate
29
+ #
30
+ # @param klass [Class, Symbol] The class which the val must be
31
+ #
32
+ # @param msg[String] A custom error message when the value is invalid
33
+ #
34
+ # @return [Boolean] the valid boolean
35
+ #
36
+ def self.json_attr(val, attr_def:, attr_name: nil)
37
+ # check that the new val is not nil if its required
38
+ val = not_nil(val, attr_name: attr_name) if attr_def[:required]
39
+
40
+ # if the new val is nil here, then nil is OK andd we shouldn't
41
+ # check anything else
42
+ return val if val.nil?
43
+
44
+ val =
45
+ case attr_def[:class]
46
+ when :Boolean
47
+ boolean val, attr_name: attr_name
48
+
49
+ when :String
50
+ fully_validate_string(val, attr_def: attr_def, attr_name: attr_name)
51
+
52
+ when :Integer
53
+ fully_validate_integer(val, attr_def: attr_def, attr_name: attr_name)
54
+
55
+ when :Number
56
+ fully_validate_number(val, attr_def: attr_def, attr_name: attr_name)
57
+
58
+ when :Hash
59
+ hash val, attr_name: attr_name
60
+
61
+ when :Symbol
62
+ symbol val, attr_name: attr_name
63
+
64
+ when :Time
65
+ time val, attr_name: attr_name
66
+
67
+ else
68
+ val
69
+ end # case
70
+
71
+ # Now that the val is in whatever correct format after the above tests,
72
+ # we test for enum membership if needed
73
+ # otherwise, just return the val
74
+ if attr_def[:enum]
75
+ in_enum val, enum: attr_def[:enum], attr_name: attr_name
76
+ else
77
+ val
78
+ end
79
+ end
80
+
81
+ # run all the possible validations on a string
82
+ def self.fully_validate_string(val, attr_def:, attr_name: nil)
83
+ val = string val, attr_name: attr_name
84
+
85
+ min_length val, min: attr_def[:min_length], attr_name: attr_name if attr_def[:min_length]
86
+ max_length val, max: attr_def[:max_length], attr_name: attr_name if attr_def[:max_length]
87
+ matches_pattern val, attr_def[:pattern], attr_name: attr_name if attr_def[:pattern]
88
+
89
+ val
90
+ end
91
+
92
+ # run all the possible validations on an integer
93
+ def self.fully_validate_integer(val, attr_def:, attr_name: nil)
94
+ val = integer val, attr_name: attr_name
95
+ validate_numeric_constraints(val, attr_def: attr_def, attr_name: attr_name)
96
+ end
97
+
98
+ # run all the possible validations on a 'number'
99
+ def self.fully_validate_number(val, attr_def:, attr_name: nil)
100
+ val =
101
+ if %w[float double].include? attr_def[:format]
102
+ float val, attr_name: attr_name
103
+ else
104
+ number val, attr_name: attr_name
105
+ end
106
+ validate_numeric_constraints(val, attr_def: attr_def, attr_name: attr_name)
107
+ end
108
+
109
+ # run the numeric constraint validations for any numeric value
110
+ # The number itself must already be validated
111
+ def self.validate_numeric_constraints(val, attr_def:, attr_name: nil)
112
+ ex_min = attr_def[:exclusive_minimum]
113
+ ex_max = attr_def[:exclusive_maximum]
114
+ mult_of = attr_def[:multiple_of]
115
+
116
+ minimum val, min: attr_def[:minimum], exclusive: ex_min, attr_name: attr_name if attr_def[:minimum]
117
+ maximum val, max: attr_def[:maximum], exclusive: ex_max, attr_name: attr_name if attr_def[:maximum]
118
+ multiple_of val, multiplier: mult_of, attr_name: attr_name if mult_of
119
+
120
+ val
121
+ end
122
+
123
+ # run the array constraint validations for an array value.
124
+ # The individual array items must already be validated
125
+ def self.array_constraints(val, attr_def:, attr_name: nil)
126
+ min_items val, min: attr_def[:minItems], attr_name: attr_name if attr_def[:minItems]
127
+ max_items val, max: attr_def[:maxItems], attr_name: attr_name if attr_def[:maxItems]
128
+ unique_array val, attr_name: attr_name if attr_def[:uniqueItems]
129
+
130
+ val
131
+ end
132
+
133
+ # validate that a value is of a specific class
134
+ #
135
+ # @param val [Object] The value to validate
136
+ #
137
+ # @param klass [Class, Symbol] The class which the val must be an instance of
138
+ #
139
+ # @param msg[String] A custom error message when the value is invalid
140
+ #
141
+ # @return [Object] the valid value
142
+ #
143
+ def self.class_instance(val, klass:, attr_name: nil, msg: nil)
144
+ return val if val.instance_of? klass
145
+
146
+ # try to instantiate the class with the value. It should raise an error
147
+ # if not good
148
+ klass.new val
149
+ rescue StandardError => e
150
+ unless msg
151
+ msg = +"#{attr_name} value must be a #{klass}, or #{klass}.new must accept it as the only parameter,"
152
+ msg << "but #{klass}.new raised: #{e.class}: #{e}"
153
+ end
154
+ raise_invalid_data_error(msg)
155
+ end
156
+
157
+ # Confirm that the given value is a boolean value, accepting
158
+ # strings and symbols and returning real booleans as needed
159
+ # Accepts: true, false, 'true', 'false', 'yes', 'no', 't','f', 'y', or 'n'
160
+ # as strings or symbols, case insensitive
161
+ #
162
+ # TODO: use this throughout ruby-jss
163
+ #
164
+ # @param val [Boolean,String,Symbol] The value to validate
165
+ #
166
+ # @param msg[String] A custom error message when the value is invalid
167
+ #
168
+ # @return [Boolean] the valid boolean
169
+ #
170
+ def self.boolean(val, attr_name: nil, msg: nil)
171
+ return val if Windoo::TRUE_FALSE.include? val
172
+ return true if val.to_s =~ /^(t(rue)?|y(es)?)$/i
173
+ return false if val.to_s =~ /^(f(alse)?|no?)$/i
174
+
175
+ raise_invalid_data_error(msg || "#{attr_name} value must be boolean true or false, or an equivalent string or symbol")
176
+ end
177
+
178
+ # Confirm that a value is an number or a string representation of an
179
+ # number. Return the number, or raise an error
180
+ #
181
+ # @param val[Object] the value to validate
182
+ #
183
+ # @param msg[String] A custom error message when the value is invalid
184
+ #
185
+ # @return [Integer]
186
+ #
187
+ def self.number(val, attr_name: nil, msg: nil)
188
+ if val.ia_a?(Integer) || val.is_a?(Float)
189
+ return val
190
+
191
+ elsif val.is_a?(String)
192
+
193
+ if val.j_integer?
194
+ return val.to_i
195
+ elsif val.j_float?
196
+ return val.to_f
197
+ end
198
+
199
+ end
200
+
201
+ raise_invalid_data_error(msg || "#{attr_name} value must be a number")
202
+ end
203
+
204
+ # Confirm that a value is an integer or a string representation of an
205
+ # integer. Return the integer, or raise an error
206
+ #
207
+ # @param val[Object] the value to validate
208
+ #
209
+ # @param msg[String] A custom error message when the value is invalid
210
+ #
211
+ # @return [Integer]
212
+ #
213
+ def self.integer(val, attr_name: nil, msg: nil)
214
+ val = val.to_i if val.is_a?(String) && val.j_integer?
215
+ return val if val.is_a? Integer
216
+
217
+ raise_invalid_data_error(msg || "#{attr_name} value must be an integer")
218
+ end
219
+
220
+ # Confirm that a value is a Float or a string representation of a Float
221
+ # Return the Float, or raise an error
222
+ #
223
+ # @param val[Object] the value to validate
224
+ #
225
+ # @param msg[String] A custom error message when the value is invalid
226
+ #
227
+ # @return [Float]
228
+ #
229
+ def self.float(val, attr_name: nil, msg: nil)
230
+ val = val.to_f if val.is_a?(Integer)
231
+ val = val.to_f if val.is_a?(String) && (val.j_float? || val.j_integer?)
232
+ return val if val.is_a? Float
233
+
234
+ raise_invalid_data_error(msg || "#{attr_name} value must be an floating point number")
235
+ end
236
+
237
+ # Confirm that a value is a Hash
238
+ # Return the Hash, or raise an error
239
+ #
240
+ # @param val[Object] the value to validate
241
+ #
242
+ # @param msg[String] A custom error message when the value is invalid
243
+ #
244
+ # @return [Hash]
245
+ #
246
+ def self.object(val, attr_name: nil, msg: nil)
247
+ return val if val.is_a? Hash
248
+
249
+ raise_invalid_data_error(msg || "#{attr_name} value must be a Hash")
250
+ end
251
+
252
+ # Confirm that a value is a Symbol, or can be coerced into one
253
+ # Return the Symbol, or raise an error
254
+ #
255
+ # @param val [#to_sym] the value to validate
256
+ #
257
+ # @param msg [String] A custom error message when the value is invalid
258
+ #
259
+ # @return [Symbol]
260
+ #
261
+ def self.symbol(val, attr_name: nil, msg: nil)
262
+ return val.to_sym if val.respond_to? :to_sym
263
+
264
+ raise_invalid_data_error(msg || "#{attr_name} value must be a Symbol")
265
+ end
266
+
267
+ # Confirm that a value is a Time, or can be parsed into
268
+ #
269
+ # Return the ISO8601 formatted String, or raise an error
270
+ #
271
+ # @param val [Time, #to_s] the value to validate
272
+ #
273
+ # @param msg [String] A custom error message when the value is invalid
274
+ #
275
+ # @return [Time] The Time
276
+ #
277
+ def self.time(val, attr_name: nil, msg: nil)
278
+ val.is_a?(Time) ? val : Time.parse(val.to_s)
279
+ rescue StandardError
280
+ raise_invalid_data_error(msg || "#{attr_name} value must be a Time, or a String to be used with Time.parse")
281
+ end
282
+
283
+ # Confirm that a value is a String
284
+ # Return the String, or raise an error
285
+ #
286
+ # @param val[Object] the value to validate
287
+ #
288
+ # @param msg[String] A custom error message when the value is invalid
289
+ #
290
+ # @param to_s: [Boolean] If true, this method always succeds and returns
291
+ # the result of calling #to_s on the value
292
+ #
293
+ # @return [Hash]
294
+ #
295
+ def self.string(val, attr_name: nil, msg: nil, to_s: false)
296
+ val = val.to_s if to_s
297
+ return val if val.is_a? String
298
+
299
+ raise_invalid_data_error(msg || "#{attr_name} value must be a String")
300
+ end
301
+
302
+ # validate that the given value is greater than or equal to some minimum
303
+ #
304
+ # If exclusive, the min value is excluded from the range and
305
+ # the value must be greater than the min.
306
+ #
307
+ # While intended for Numbers, this will work for any Comparable objects
308
+ #
309
+ # @param val [Object] the thing to validate
310
+ #
311
+ # @param min [Object] A value that the val must be greater than or equal to
312
+ #
313
+ # @param exclusuve [Boolean] Should the min be excluded from the valid range?
314
+ # true: val must be > min, false: val must be >= min
315
+ #
316
+ # @param msg [String] A custom error message when the value is invalid
317
+ #
318
+ # @return [String] the valid value
319
+ #
320
+ def self.minimum(val, min:, attr_name: nil, exclusive: false, msg: nil)
321
+ if exclusive
322
+ return val if val > min
323
+ elsif val >= min
324
+ return val
325
+ end
326
+ raise_invalid_data_error(msg || "#{attr_name} value must be >= #{min}")
327
+ end
328
+
329
+ # validate that the given value is less than or equal to some maximum
330
+ #
331
+ # While intended for Numbers, this will work for any Comparable objects
332
+ #
333
+ # If exclusive, the max value is excluded from the range and
334
+ # the value must be less than the max.
335
+ #
336
+ # @param val [Object] the thing to validate
337
+ #
338
+ # @param max[Object] A value that the val must be less than or equal to
339
+ #
340
+ # @param exclusuve [Boolean] Should the max be excluded from the valid range?
341
+ # true: val must be < max, false: val must be <= max
342
+ #
343
+ # @param msg[String] A custom error message when the value is invalid
344
+ #
345
+ # @return [String] the valid value
346
+ #
347
+ def self.maximum(val, max:, attr_name: nil, exclusive: false, msg: nil)
348
+ if exclusive
349
+ return val if val < max
350
+ elsif val <= max
351
+ return val
352
+ end
353
+ raise_invalid_data_error(msg || "#{attr_name} value must be <= #{max}")
354
+ end
355
+
356
+ # Validate that a given number is multiple of some other given number
357
+ #
358
+ # @param val [Number] the number to validate
359
+ #
360
+ # @param multiplier [Number] the number what the val must be a multiple of.
361
+ # this must be positive.
362
+ #
363
+ # @param msg[String] A custom error message when the value is invalid
364
+ #
365
+ # @return [String] the valid value
366
+ #
367
+ def self.multiple_of(val, multiplier:, attr_name: nil, msg: nil)
368
+ unless multiplier.is_a?(Numeric) && multiplier.positive?
369
+ raise ArgumentError,
370
+ 'multiplier must be a positive number'
371
+ end
372
+ raise Windoo::InvalidDataError, 'Value must be a number' unless val.is_a?(Numeric)
373
+
374
+ return val if (val % multiplier).zero?
375
+
376
+ raise_invalid_data_error(msg || "#{attr_name} value must be a multiple of #{multiplier}")
377
+ end
378
+
379
+ # validate that the given value's length is greater than or equal to some minimum
380
+ #
381
+ # While this is intended for Strings, it will work for any object that responds
382
+ # to #length
383
+ #
384
+ # @param val [Object] the value to validate
385
+ #
386
+ # @param min [Object] The minimum length allowed
387
+ #
388
+ # @param msg [String] A custom error message when the value is invalid
389
+ #
390
+ # @return [String] the valid value
391
+ #
392
+ def self.min_length(val, min:, attr_name: nil, msg: nil)
393
+ raise ArgumentError, 'min must be a number' unless min.is_a?(Numeric)
394
+ return val if val.length >= min
395
+
396
+ raise_invalid_data_error(msg || "length of #{attr_name} value must be >= #{min}")
397
+ end
398
+
399
+ # validate that the given value's length is less than or equal to some maximum
400
+ #
401
+ # While this is intended for Strings, it will work for any object that responds
402
+ # to #length
403
+ #
404
+ # @param val [Object] the value to validate
405
+ #
406
+ # @param max [Object] the maximum length allowed
407
+ #
408
+ # @param msg [String] A custom error message when the value is invalid
409
+ #
410
+ # @return [String] the valid value
411
+ #
412
+ def self.max_length(val, max:, attr_name: nil, msg: nil)
413
+ raise ArgumentError, 'max must be a number' unless max.is_a?(Numeric)
414
+ return val if val.length <= max
415
+
416
+ raise_invalid_data_error(msg || "length of #{attr_name} value must be <= #{max}")
417
+ end
418
+
419
+ # validate that the given value contains at least some minimum number of items
420
+ #
421
+ # While this is intended for Arrays, it will work for any object that responds
422
+ # to #size
423
+ #
424
+ # @param val [Object] the value to validate
425
+ #
426
+ # @param min [Object] the minimum number of items allowed
427
+ #
428
+ # @param msg [String] A custom error message when the value is invalid
429
+ #
430
+ # @return [String] the valid value
431
+ #
432
+ def self.min_items(val, min:, attr_name: nil, msg: nil)
433
+ raise ArgumentError, 'min must be a number' unless min.is_a?(Numeric)
434
+ return val if val.size >= min
435
+
436
+ raise_invalid_data_error(msg || "#{attr_name} value must contain at least #{min} items")
437
+ end
438
+
439
+ # validate that the given value contains no more than some maximum number of items
440
+ #
441
+ # While this is intended for Arrays, it will work for any object that responds
442
+ # to #size
443
+ #
444
+ # @param val [Object] the value to validate
445
+ #
446
+ # @param max [Object] the maximum number of items allowed
447
+ #
448
+ # @param msg [String] A custom error message when the value is invalid
449
+ #
450
+ # @return [String] the valid value
451
+ #
452
+ def self.max_items(val, max:, attr_name: nil, msg: nil)
453
+ raise ArgumentError, 'max must be a number' unless max.is_a?(Numeric)
454
+ return val if val.size <= max
455
+
456
+ raise_invalid_data_error(msg || "#{attr_name} value must contain no more than #{max} items")
457
+ end
458
+
459
+ # validate that an array has only unique items, no duplicate values
460
+ #
461
+ # @param val [Array] The array to validate
462
+ #
463
+ # @param msg [String] A custom error message when the value is invalid
464
+ #
465
+ # @param return [Array] the valid array
466
+ #
467
+ def self.unique_array(val, attr_name: nil, msg: nil)
468
+ raise ArgumentError, 'Value must be an Array' unless val.is_a?(Array)
469
+ return val if val.uniq.size == val.size
470
+
471
+ raise_invalid_data_error(msg || "#{attr_name} value must contain only unique items")
472
+ end
473
+
474
+ # validate that a value is not nil
475
+ #
476
+ # @param val[Object] the value to validate
477
+ #
478
+ # @param msg[String] A custom error message when the value is invalid
479
+ #
480
+ # @return [Object] the valid value
481
+ #
482
+ def self.not_nil(val, attr_name: nil, msg: nil)
483
+ return val unless val.nil?
484
+
485
+ raise_invalid_data_error(msg || "#{attr_name} value may not be nil")
486
+ end
487
+
488
+ # Does a value exist in a given enum array?
489
+ #
490
+ # @param val [Object] The thing that must be in the enum
491
+ #
492
+ # @param enum [Array] the enum of allowed values
493
+ #
494
+ # @param msg[String] A custom error message when the value is invalid
495
+ #
496
+ # @return [Object] The valid object
497
+ #
498
+ def self.in_enum(val, enum:, attr_name: nil, msg: nil)
499
+ return val if enum.include? val
500
+
501
+ raise_invalid_data_error(msg || "#{attr_name} value must be one of: #{enum.join ', '}")
502
+ end
503
+
504
+ # Does a string match a given regular expression?
505
+ #
506
+ # @param val [String] The value to match
507
+ #
508
+ # @param pattern [pattern] the regular expression
509
+ #
510
+ # @param msg[String] A custom error message when the value is invalid
511
+ #
512
+ # @return [Object] The valid object
513
+ #
514
+ def self.matches_pattern(val, pattern:, attr_name: nil, msg: nil)
515
+ return val if val =~ pattern
516
+
517
+ raise_invalid_data_error(msg || "#{attr_name} value does not match RegExp: #{pattern}")
518
+ end
519
+
520
+ # confirm we were given a valid container when creating
521
+ #
522
+ # @param new_object_class [Class] the class of the new object we are creating
523
+ #
524
+ # @param container [Object] The object that will contain tne new object
525
+ # we are creating
526
+ #
527
+ # @return [Object, nil] The valid container for the new object
528
+ #
529
+ def self.container_for_new_object(new_object_class:, container: nil)
530
+ # software titles don't have containers.
531
+ return if new_object_class == Windoo::SoftwareTitle
532
+
533
+ unless container
534
+ raise Windoo::UnsupportedError,
535
+ 'All new objects other than SoftwareTitle must pass in a container: object'
536
+ end
537
+
538
+ unless new_object_class::CONTAINER_CLASS == container.class
539
+ raise Windoo::InvalidDataError,
540
+ "The container for new #{new_object_class} objects must be a #{new_object_class::CONTAINER_CLASS} object"
541
+ end
542
+
543
+ container
544
+ end
545
+
546
+ end # module validate
547
+
548
+ end # module Windoo
@@ -0,0 +1,15 @@
1
+ # Copyright 2025 Pixar
2
+ #
3
+ # Licensed under the terms set forth in the LICENSE.txt file available at
4
+ # at the root of this project.
5
+ #
6
+ #
7
+
8
+ # frozen_string_literal: true
9
+
10
+ # main module
11
+ module Windoo
12
+
13
+ VERSION = '1.0.1'
14
+
15
+ end # module