json-schema-pvdgm 2.3.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 (96) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +19 -0
  3. data/README.textile +354 -0
  4. data/lib/json-schema.rb +25 -0
  5. data/lib/json-schema/attributes/additionalitems.rb +23 -0
  6. data/lib/json-schema/attributes/additionalproperties.rb +67 -0
  7. data/lib/json-schema/attributes/allof.rb +37 -0
  8. data/lib/json-schema/attributes/anyof.rb +41 -0
  9. data/lib/json-schema/attributes/dependencies.rb +30 -0
  10. data/lib/json-schema/attributes/dependencies_v4.rb +20 -0
  11. data/lib/json-schema/attributes/disallow.rb +11 -0
  12. data/lib/json-schema/attributes/divisibleby.rb +16 -0
  13. data/lib/json-schema/attributes/enum.rb +24 -0
  14. data/lib/json-schema/attributes/extends.rb +49 -0
  15. data/lib/json-schema/attributes/format.rb +123 -0
  16. data/lib/json-schema/attributes/items.rb +25 -0
  17. data/lib/json-schema/attributes/maxdecimal.rb +15 -0
  18. data/lib/json-schema/attributes/maximum.rb +15 -0
  19. data/lib/json-schema/attributes/maximum_inclusive.rb +15 -0
  20. data/lib/json-schema/attributes/maxitems.rb +12 -0
  21. data/lib/json-schema/attributes/maxlength.rb +14 -0
  22. data/lib/json-schema/attributes/maxproperties.rb +12 -0
  23. data/lib/json-schema/attributes/minimum.rb +15 -0
  24. data/lib/json-schema/attributes/minimum_inclusive.rb +15 -0
  25. data/lib/json-schema/attributes/minitems.rb +12 -0
  26. data/lib/json-schema/attributes/minlength.rb +14 -0
  27. data/lib/json-schema/attributes/minproperties.rb +12 -0
  28. data/lib/json-schema/attributes/multipleof.rb +16 -0
  29. data/lib/json-schema/attributes/not.rb +28 -0
  30. data/lib/json-schema/attributes/oneof.rb +32 -0
  31. data/lib/json-schema/attributes/pattern.rb +15 -0
  32. data/lib/json-schema/attributes/patternproperties.rb +23 -0
  33. data/lib/json-schema/attributes/properties.rb +58 -0
  34. data/lib/json-schema/attributes/properties_optional.rb +23 -0
  35. data/lib/json-schema/attributes/properties_v4.rb +57 -0
  36. data/lib/json-schema/attributes/ref.rb +70 -0
  37. data/lib/json-schema/attributes/required.rb +23 -0
  38. data/lib/json-schema/attributes/type.rb +102 -0
  39. data/lib/json-schema/attributes/type_v4.rb +54 -0
  40. data/lib/json-schema/attributes/uniqueitems.rb +16 -0
  41. data/lib/json-schema/model_validator.rb +85 -0
  42. data/lib/json-schema/schema.rb +73 -0
  43. data/lib/json-schema/uri/file.rb +36 -0
  44. data/lib/json-schema/uri/uuid.rb +285 -0
  45. data/lib/json-schema/util/array_set.rb +14 -0
  46. data/lib/json-schema/util/hash.rb +8 -0
  47. data/lib/json-schema/validator.rb +672 -0
  48. data/lib/json-schema/validators/draft1.rb +32 -0
  49. data/lib/json-schema/validators/draft2.rb +33 -0
  50. data/lib/json-schema/validators/draft3.rb +38 -0
  51. data/lib/json-schema/validators/draft4.rb +45 -0
  52. data/resources/draft-01.json +155 -0
  53. data/resources/draft-02.json +166 -0
  54. data/resources/draft-03.json +174 -0
  55. data/resources/draft-04.json +150 -0
  56. data/test/data/all_of_ref_data.json +3 -0
  57. data/test/data/any_of_ref_data.json +7 -0
  58. data/test/data/bad_data_1.json +3 -0
  59. data/test/data/good_data_1.json +3 -0
  60. data/test/data/one_of_ref_links_data.json +5 -0
  61. data/test/schemas/all_of_ref_base_schema.json +6 -0
  62. data/test/schemas/all_of_ref_schema.json +7 -0
  63. data/test/schemas/any_of_ref_jane_schema.json +4 -0
  64. data/test/schemas/any_of_ref_jimmy_schema.json +4 -0
  65. data/test/schemas/any_of_ref_john_schema.json +4 -0
  66. data/test/schemas/any_of_ref_schema.json +15 -0
  67. data/test/schemas/extends_and_additionalProperties-1-filename.schema.json +34 -0
  68. data/test/schemas/extends_and_additionalProperties-1-ref.schema.json +34 -0
  69. data/test/schemas/extends_and_additionalProperties-2-filename.schema.json +33 -0
  70. data/test/schemas/extends_and_additionalProperties-2-ref.schema.json +33 -0
  71. data/test/schemas/good_schema_1.json +10 -0
  72. data/test/schemas/good_schema_2.json +10 -0
  73. data/test/schemas/good_schema_extends1.json +10 -0
  74. data/test/schemas/good_schema_extends2.json +13 -0
  75. data/test/schemas/inner.schema.json +21 -0
  76. data/test/schemas/one_of_ref_links_schema.json +16 -0
  77. data/test/schemas/self_link_schema.json +17 -0
  78. data/test/schemas/up_link_schema.json +17 -0
  79. data/test/test_all_of_ref_schema.rb +11 -0
  80. data/test/test_any_of_ref_schema.rb +11 -0
  81. data/test/test_bad_schema_ref.rb +33 -0
  82. data/test/test_extended_schema.rb +68 -0
  83. data/test/test_extends_and_additionalProperties.rb +50 -0
  84. data/test/test_files_v3.rb +52 -0
  85. data/test/test_fragment_resolution.rb +31 -0
  86. data/test/test_full_validation.rb +209 -0
  87. data/test/test_jsonschema_draft1.rb +701 -0
  88. data/test/test_jsonschema_draft2.rb +773 -0
  89. data/test/test_jsonschema_draft3.rb +1236 -0
  90. data/test/test_jsonschema_draft4.rb +1356 -0
  91. data/test/test_model_validator.rb +52 -0
  92. data/test/test_one_of.rb +42 -0
  93. data/test/test_ruby_schema.rb +38 -0
  94. data/test/test_schema_type_attribute.rb +21 -0
  95. data/test/test_schema_validation.rb +85 -0
  96. metadata +180 -0
@@ -0,0 +1,102 @@
1
+ module JSON
2
+ class Schema
3
+ class TypeAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
+ union = true
6
+ if options[:disallow]
7
+ types = current_schema.schema['disallow']
8
+ else
9
+ types = current_schema.schema['type']
10
+ end
11
+
12
+ if !types.is_a?(Array)
13
+ types = [types]
14
+ union = false
15
+ end
16
+ valid = false
17
+
18
+ # Create an array to hold errors that are generated during union validation
19
+ union_errors = []
20
+
21
+ types.each do |type|
22
+ if type.is_a?(String)
23
+ valid = data_valid_for_type?(data, type)
24
+ elsif type.is_a?(Hash) && union
25
+ # Validate as a schema
26
+ schema = JSON::Schema.new(type,current_schema.uri,validator)
27
+
28
+ # We're going to add a little cruft here to try and maintain any validation errors that occur in this union type
29
+ # We'll handle this by keeping an error count before and after validation, extracting those errors and pushing them onto a union error
30
+ pre_validation_error_count = validation_errors(processor).count
31
+
32
+ begin
33
+ schema.validate(data,fragments,processor,options)
34
+ valid = true
35
+ rescue ValidationError
36
+ # We don't care that these schemas don't validate - we only care that one validated
37
+ end
38
+
39
+ diff = validation_errors(processor).count - pre_validation_error_count
40
+ valid = false if diff > 0
41
+ while diff > 0
42
+ diff = diff - 1
43
+ union_errors.push(validation_errors(processor).pop)
44
+ end
45
+ end
46
+
47
+ break if valid
48
+ end
49
+
50
+ if (options[:disallow])
51
+ if valid
52
+ message = "The property '#{build_fragment(fragments)}' matched one or more of the following types:"
53
+ types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
54
+ message.chop!
55
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors], { property: last_fragment_as_symbol(fragments), failure: :type })
56
+ end
57
+ elsif !valid
58
+ if union
59
+ message = "The property '#{build_fragment(fragments)}' of type #{type_of_data(data)} did not match one or more of the following types:"
60
+ types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
61
+ message.chop!
62
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors], { property: last_fragment_as_symbol(fragments), failure: :type })
63
+ validation_errors(processor).last.sub_errors = union_errors
64
+ else
65
+ message = "The property '#{build_fragment(fragments)}' of type #{type_of_data(data)} did not match the following type:"
66
+ types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
67
+ message.chop!
68
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors], { property: last_fragment_as_symbol(fragments), failure: :type })
69
+ end
70
+ end
71
+ end
72
+
73
+ TYPE_CLASS_MAPPINGS = {
74
+ "string" => String,
75
+ "number" => Numeric,
76
+ "integer" => Integer,
77
+ "boolean" => [TrueClass, FalseClass],
78
+ "object" => Hash,
79
+ "array" => Array,
80
+ "null" => NilClass,
81
+ "any" => Object
82
+ }
83
+
84
+ def self.data_valid_for_type?(data, type)
85
+ valid_classes = TYPE_CLASS_MAPPINGS.fetch(type) { return true }
86
+ Array(valid_classes).any? { |c| data.is_a?(c) }
87
+ end
88
+
89
+ # Lookup Schema type of given class instance
90
+ def self.type_of_data(data)
91
+ type, klass = TYPE_CLASS_MAPPINGS.map { |k,v| [k,v] }.sort_by { |i|
92
+ k,v = i
93
+ -Array(v).map { |klass| klass.ancestors.size }.max
94
+ }.find { |i|
95
+ k,v = i
96
+ Array(v).any? { |klass| data.kind_of?(klass) }
97
+ }
98
+ type
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,54 @@
1
+ module JSON
2
+ class Schema
3
+ class TypeV4Attribute < Attribute
4
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
+ union = true
6
+ types = current_schema.schema['type']
7
+ if !types.is_a?(Array)
8
+ types = [types]
9
+ union = false
10
+ end
11
+ valid = false
12
+
13
+ # Create an array to hold errors that are generated during union validation
14
+ union_errors = []
15
+
16
+ types.each do |type|
17
+ valid = data_valid_for_type?(data, type)
18
+ break if valid
19
+ end
20
+
21
+ if !valid
22
+ if union
23
+ message = "The property '#{build_fragment(fragments)}' of type #{data.class} did not match one or more of the following types:"
24
+ types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
25
+ message.chop!
26
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors], { property: last_fragment_as_symbol(fragments), failure: :type })
27
+ validation_errors(processor).last.sub_errors = union_errors
28
+ else
29
+ message = "The property '#{build_fragment(fragments)}' of type #{data.class} did not match the following type:"
30
+ types.each {|type| message += type.is_a?(String) ? " #{type}," : " (schema)," }
31
+ message.chop!
32
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors], { property: last_fragment_as_symbol(fragments), failure: :type })
33
+ end
34
+ end
35
+ end
36
+
37
+ TYPE_CLASS_MAPPINGS = {
38
+ "string" => String,
39
+ "number" => Numeric,
40
+ "integer" => Integer,
41
+ "boolean" => [TrueClass, FalseClass],
42
+ "object" => Hash,
43
+ "array" => Array,
44
+ "null" => NilClass,
45
+ "any" => Object
46
+ }
47
+
48
+ def self.data_valid_for_type?(data, type)
49
+ valid_classes = TYPE_CLASS_MAPPINGS.fetch(type) { return true }
50
+ Array(valid_classes).any? { |c| data.is_a?(c) }
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,16 @@
1
+ module JSON
2
+ class Schema
3
+ class UniqueItemsAttribute < Attribute
4
+ def self.validate(current_schema, data, fragments, processor, validator, options = {})
5
+ if data.is_a?(Array)
6
+ d = data.clone
7
+ dupes = d.uniq!
8
+ if dupes
9
+ message = "The property '#{build_fragment(fragments)}' contained duplicated array values"
10
+ validation_error(processor, message, fragments, current_schema, self, options[:record_errors], { property: last_fragment_as_symbol(fragments), failure: :unique })
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,85 @@
1
+ module JSON
2
+
3
+ class ModelValidator
4
+
5
+ attr_reader :schema
6
+
7
+ DYNAMIC_VALIDATORS = [
8
+ [ [ 'requiredIf', :requiredIf ], :process_required_if ],
9
+ [ [ 'noneOfTheAbove', :noneOfTheAbove ], :process_none_of_the_above ]
10
+ ]
11
+
12
+ def initialize(schema)
13
+ if schema.is_a?(String)
14
+ @schema = JSON.parse(File.new(schema).read)
15
+ elsif schema.is_a?(Hash)
16
+ @schema = schema
17
+ else
18
+ raise "Schema must be a path or a hash!"
19
+ end
20
+ end
21
+
22
+ #
23
+ # Validate the specified model having the hash data contained
24
+ # in the hash_attribute attribute. By default the hash data
25
+ # is assumed to be located in the model's 'responses'
26
+ # attribute.
27
+ #
28
+ def validate(model, hash_attribute=:responses)
29
+ data = model[hash_attribute] || {}
30
+
31
+ # Put the hash data into an OpenStruct to allow access to
32
+ # the hash data as an object. We're using the short variable
33
+ # name of 'r' because that's what we're using in the schema
34
+ # definition; it keeps the schema stuff short and to the point.
35
+ r = OpenStruct.new(data)
36
+
37
+ m = model
38
+
39
+ # Do the static validation first
40
+ errors = JSON::Validator.fully_validate(schema, data, errors_as_objects: true).map { | e | e[:error_details] }.uniq
41
+
42
+ # Now perform dynamic validation
43
+ schema['properties'].each_pair do | property, sch |
44
+ errors.concat process_dynamic_validation(property, sch, r, m)
45
+ end
46
+
47
+ # Add the errors to the model
48
+ errors.each do | error |
49
+ model.errors.add(error[:property], stringize_error(error))
50
+ end
51
+
52
+ errors
53
+ end
54
+
55
+ def stringize_error(error)
56
+ s = "#{error[:property]}_#{error[:failure]}"
57
+ defined?(I18n) ? I18n.t(s) : s
58
+ end
59
+
60
+ def process_dynamic_validation(property, sch, r, m)
61
+ errors = []
62
+ DYNAMIC_VALIDATORS.each do | dv |
63
+ dv_value = sch[dv.first.first] || sch[dv.first.last]
64
+ error = send(dv.last, m, r, property, dv_value) if dv_value.present?
65
+ errors << error if error.present?
66
+ end
67
+ errors
68
+ end
69
+
70
+ def process_required_if(m, r, property, required_if_expression)
71
+ if eval(required_if_expression)
72
+ { property: property.to_sym, failure: :required } if r[property.to_sym].blank?
73
+ end
74
+ end
75
+
76
+ def process_none_of_the_above(m, r, property, none_of_the_above_value)
77
+ value = r[property.to_sym]
78
+ if value.is_a?(Array)
79
+ { property: property.to_sym, failure: :noneOfTheAbove } if value.include?(none_of_the_above_value) && value.size > 1
80
+ end
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,73 @@
1
+ require 'pathname'
2
+
3
+ module JSON
4
+ class Schema
5
+
6
+ attr_accessor :schema, :uri, :validator
7
+
8
+ def initialize(schema,uri,parent_validator=nil)
9
+ @schema = schema
10
+ @uri = uri
11
+
12
+ self.class.add_indifferent_access(@schema)
13
+
14
+ # If there is an ID on this schema, use it to generate the URI
15
+ if @schema['id'] && @schema['id'].kind_of?(String)
16
+ temp_uri = URI.parse(@schema['id'])
17
+ if temp_uri.relative?
18
+ uri = uri.merge(@schema['id'])
19
+ temp_uri = uri
20
+ end
21
+ @uri = temp_uri
22
+ end
23
+ @uri.fragment = ''
24
+
25
+ # If there is a $schema on this schema, use it to determine which validator to use
26
+ if @schema['$schema']
27
+ u = URI.parse(@schema['$schema'])
28
+ @validator = JSON::Validator.validators["#{u.scheme}://#{u.host}#{u.path}"]
29
+ if @validator.nil?
30
+ raise SchemaError.new("This library does not have support for schemas defined by #{u.scheme}://#{u.host}#{u.path}")
31
+ end
32
+ elsif parent_validator
33
+ @validator = parent_validator
34
+ else
35
+ @validator = JSON::Validator.default_validator
36
+ end
37
+ end
38
+
39
+ def validate(data, fragments, processor, options = {})
40
+ @validator.validate(self, data, fragments, processor, options)
41
+ end
42
+
43
+ def self.add_indifferent_access(schema)
44
+ if schema.is_a?(Hash)
45
+ schema.default_proc = proc do |hash,key|
46
+ if hash.has_key?(key)
47
+ hash[key]
48
+ else
49
+ key = case key
50
+ when Symbol then key.to_s
51
+ when String then key.to_sym
52
+ end
53
+ hash.has_key?(key) ? hash[key] : nil
54
+ end
55
+ end
56
+ schema.keys.each do |key|
57
+ add_indifferent_access(schema[key])
58
+ end
59
+ end
60
+ end
61
+
62
+ def base_uri
63
+ parts = @uri.to_s.split('/')
64
+ parts.pop
65
+ parts.join('/') + '/'
66
+ end
67
+
68
+ def to_s
69
+ @schema.to_json
70
+ end
71
+ end
72
+ end
73
+
@@ -0,0 +1,36 @@
1
+ require 'rbconfig'
2
+ require 'uri'
3
+
4
+ module URI
5
+
6
+ # Ruby does not have built-in support for filesystem URIs, and definitely does not have built-in support for
7
+ # using open-uri with filesystem URIs
8
+ class File < Generic
9
+
10
+ COMPONENT = [
11
+ :scheme,
12
+ :path,
13
+ :fragment,
14
+ :host
15
+ ].freeze
16
+
17
+ def initialize(*arg)
18
+ # arg[2] is the 'host'; this logic to set it to "" causes file schemes with UNC to break
19
+ # so don't do it on windows platforms
20
+ is_windows = (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/)
21
+ arg[2] = "" unless is_windows
22
+ super(*arg)
23
+ end
24
+
25
+ def self.build(args)
26
+ tmp = Util::make_components_hash(self, args)
27
+ return super(tmp)
28
+ end
29
+
30
+ def open(*rest, &block)
31
+ ::File.open(self.path, *rest, &block)
32
+ end
33
+
34
+ @@schemes['FILE'] = File
35
+ end
36
+ end
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env ruby
2
+ ### http://mput.dip.jp/mput/uuid.txt
3
+
4
+ # Copyright(c) 2005 URABE, Shyouhei.
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this code, to deal in the code without restriction, including without
8
+ # limitation the rights to use, copy, modify, merge, publish, distribute,
9
+ # sublicense, and/or sell copies of the code, and to permit persons to whom the
10
+ # code is furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the code.
14
+ #
15
+ # THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE CODE OR THE USE OR OTHER DEALINGS IN THE
21
+ # CODE.
22
+ #
23
+ # 2009-02-20: Modified by Pablo Lorenzoni <pablo@propus.com.br> to correctly
24
+ # include the version in the raw_bytes.
25
+
26
+
27
+ require 'digest/md5'
28
+ require 'digest/sha1'
29
+ require 'tmpdir'
30
+
31
+ module JSON
32
+ module Util
33
+
34
+ # Pure ruby UUID generator, which is compatible with RFC4122
35
+ UUID = Struct.new :raw_bytes
36
+
37
+ class UUID
38
+ private_class_method :new
39
+
40
+ class << self
41
+ def mask19 v, str # :nodoc
42
+ nstr = str.bytes.to_a
43
+ version = [0, 16, 32, 48, 64, 80][v]
44
+ nstr[6] &= 0b00001111
45
+ nstr[6] |= version
46
+ # nstr[7] &= 0b00001111
47
+ # nstr[7] |= 0b01010000
48
+ nstr[8] &= 0b00111111
49
+ nstr[8] |= 0b10000000
50
+ str = ''
51
+ nstr.each { |s| str << s.chr }
52
+ str
53
+ end
54
+
55
+ def mask18 v, str # :nodoc
56
+ version = [0, 16, 32, 48, 64, 80][v]
57
+ str[6] &= 0b00001111
58
+ str[6] |= version
59
+ # str[7] &= 0b00001111
60
+ # str[7] |= 0b01010000
61
+ str[8] &= 0b00111111
62
+ str[8] |= 0b10000000
63
+ str
64
+ end
65
+
66
+ def mask v, str
67
+ if RUBY_VERSION >= "1.9.0"
68
+ return mask19 v, str
69
+ else
70
+ return mask18 v, str
71
+ end
72
+ end
73
+ private :mask, :mask18, :mask19
74
+
75
+ # UUID generation using SHA1. Recommended over create_md5.
76
+ # Namespace object is another UUID, some of them are pre-defined below.
77
+ def create_sha1 str, namespace
78
+ sha1 = Digest::SHA1.new
79
+ sha1.update namespace.raw_bytes
80
+ sha1.update str
81
+ sum = sha1.digest
82
+ raw = mask 5, sum[0..15]
83
+ ret = new raw
84
+ ret.freeze
85
+ ret
86
+ end
87
+ alias :create_v5 :create_sha1
88
+
89
+ # UUID generation using MD5 (for backward compat.)
90
+ def create_md5 str, namespace
91
+ md5 = Digest::MD5.new
92
+ md5.update namespace.raw_bytes
93
+ md5.update str
94
+ sum = md5.digest
95
+ raw = mask 3, sum[0..16]
96
+ ret = new raw
97
+ ret.freeze
98
+ ret
99
+ end
100
+ alias :create_v3 :create_md5
101
+
102
+ # UUID generation using random-number generator. From it's random
103
+ # nature, there's no warranty that the created ID is really universaly
104
+ # unique.
105
+ def create_random
106
+ rnd = [
107
+ rand(0x100000000),
108
+ rand(0x100000000),
109
+ rand(0x100000000),
110
+ rand(0x100000000),
111
+ ].pack "N4"
112
+ raw = mask 4, rnd
113
+ ret = new raw
114
+ ret.freeze
115
+ ret
116
+ end
117
+ alias :create_v4 :create_random
118
+
119
+ def read_state fp # :nodoc:
120
+ fp.rewind
121
+ Marshal.load fp.read
122
+ end
123
+
124
+ def write_state fp, c, m # :nodoc:
125
+ fp.rewind
126
+ str = Marshal.dump [c, m]
127
+ fp.write str
128
+ end
129
+
130
+ private :read_state, :write_state
131
+ STATE_FILE = 'ruby-uuid'
132
+
133
+ # create the "version 1" UUID with current system clock, current UTC
134
+ # timestamp, and the IEEE 802 address (so-called MAC address).
135
+ #
136
+ # Speed notice: it's slow. It writes some data into hard drive on every
137
+ # invokation. If you want to speed this up, try remounting tmpdir with a
138
+ # memory based filesystem (such as tmpfs). STILL slow? then no way but
139
+ # rewrite it with c :)
140
+ def create clock=nil, time=nil, mac_addr=nil
141
+ c = t = m = nil
142
+ Dir.chdir Dir.tmpdir do
143
+ unless FileTest.exist? STATE_FILE then
144
+ # Generate a pseudo MAC address because we have no pure-ruby way
145
+ # to know the MAC address of the NIC this system uses. Note
146
+ # that cheating with pseudo arresses here is completely legal:
147
+ # see Section 4.5 of RFC4122 for details.
148
+ sha1 = Digest::SHA1.new
149
+ 256.times do
150
+ r = [rand(0x100000000)].pack "N"
151
+ sha1.update r
152
+ end
153
+ str = sha1.digest
154
+ r = rand 14 # 20-6
155
+ node = str[r, 6] || str
156
+ if RUBY_VERSION >= "1.9.0"
157
+ nnode = node.bytes.to_a
158
+ nnode[0] |= 0x01
159
+ node = ''
160
+ nnode.each { |s| node << s.chr }
161
+ else
162
+ node[0] |= 0x01 # multicast bit
163
+ end
164
+ k = rand 0x40000
165
+ open STATE_FILE, 'w' do |fp|
166
+ fp.flock IO::LOCK_EX
167
+ write_state fp, k, node
168
+ fp.chmod 0o777 # must be world writable
169
+ end
170
+ end
171
+ open STATE_FILE, 'r+' do |fp|
172
+ fp.flock IO::LOCK_EX
173
+ c, m = read_state fp
174
+ c = clock % 0x4000 if clock
175
+ m = mac_addr if mac_addr
176
+ t = time
177
+ if t.nil? then
178
+ # UUID epoch is 1582/Oct/15
179
+ tt = Time.now
180
+ t = tt.to_i*10000000 + tt.tv_usec*10 + 0x01B21DD213814000
181
+ end
182
+ c = c.succ # important; increment here
183
+ write_state fp, c, m
184
+ end
185
+ end
186
+
187
+ tl = t & 0xFFFF_FFFF
188
+ tm = t >> 32
189
+ tm = tm & 0xFFFF
190
+ th = t >> 48
191
+ th = th & 0x0FFF
192
+ th = th | 0x1000
193
+ cl = c & 0xFF
194
+ ch = c & 0x3F00
195
+ ch = ch >> 8
196
+ ch = ch | 0x80
197
+ pack tl, tm, th, cl, ch, m
198
+ end
199
+ alias :create_v1 :create
200
+
201
+ # A simple GUID parser: just ignores unknown characters and convert
202
+ # hexadecimal dump into 16-octet object.
203
+ def parse obj
204
+ str = obj.to_s.sub %r/\Aurn:uuid:/, ''
205
+ str.gsub! %r/[^0-9A-Fa-f]/, ''
206
+ raw = str[0..31].lines.to_a.pack 'H*'
207
+ ret = new raw
208
+ ret.freeze
209
+ ret
210
+ end
211
+
212
+ # The 'primitive constructor' of this class
213
+ # Note UUID.pack(uuid.unpack) == uuid
214
+ def pack tl, tm, th, ch, cl, n
215
+ raw = [tl, tm, th, ch, cl, n].pack "NnnCCa6"
216
+ ret = new raw
217
+ ret.freeze
218
+ ret
219
+ end
220
+ end
221
+
222
+ # The 'primitive deconstructor', or the dual to pack.
223
+ # Note UUID.pack(uuid.unpack) == uuid
224
+ def unpack
225
+ raw_bytes.unpack "NnnCCa6"
226
+ end
227
+
228
+ # Generate the string representation (a.k.a GUID) of this UUID
229
+ def to_s
230
+ a = unpack
231
+ tmp = a[-1].unpack 'C*'
232
+ a[-1] = sprintf '%02x%02x%02x%02x%02x%02x', *tmp
233
+ "%08x-%04x-%04x-%02x%02x-%s" % a
234
+ end
235
+ alias guid to_s
236
+
237
+ # Convert into a RFC4122-comforming URN representation
238
+ def to_uri
239
+ "urn:uuid:" + self.to_s
240
+ end
241
+ alias urn to_uri
242
+
243
+ # Convert into 128-bit unsigned integer
244
+ # Typically a Bignum instance, but can be a Fixnum.
245
+ def to_int
246
+ tmp = self.raw_bytes.unpack "C*"
247
+ tmp.inject do |r, i|
248
+ r * 256 | i
249
+ end
250
+ end
251
+ alias to_i to_int
252
+
253
+ # Gets the version of this UUID
254
+ # returns nil if bad version
255
+ def version
256
+ a = unpack
257
+ v = (a[2] & 0xF000).to_s(16)[0].chr.to_i
258
+ return v if (1..5).include? v
259
+ return nil
260
+ end
261
+
262
+ # Two UUIDs are said to be equal if and only if their (byte-order
263
+ # canonicalized) integer representations are equivallent. Refer RFC4122 for
264
+ # details.
265
+ def == other
266
+ to_i == other.to_i
267
+ end
268
+
269
+ include Comparable
270
+ # UUIDs are comparable (don't know what benefits are there, though).
271
+ def <=> other
272
+ to_s <=> other.to_s
273
+ end
274
+
275
+ # Pre-defined UUID Namespaces described in RFC4122 Appendix C.
276
+ NameSpace_DNS = parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
277
+ NameSpace_URL = parse "6ba7b811-9dad-11d1-80b4-00c04fd430c8"
278
+ NameSpace_OID = parse "6ba7b812-9dad-11d1-80b4-00c04fd430c8"
279
+ NameSpace_X500 = parse "6ba7b814-9dad-11d1-80b4-00c04fd430c8"
280
+
281
+ # The Nil UUID in RFC4122 Section 4.1.7
282
+ Nil = parse "00000000-0000-0000-0000-000000000000"
283
+ end
284
+ end
285
+ end