json-schema-ouidou 2.9.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 (83) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +19 -0
  3. data/README.md +496 -0
  4. data/lib/json-schema/attribute.rb +57 -0
  5. data/lib/json-schema/attributes/additionalitems.rb +28 -0
  6. data/lib/json-schema/attributes/additionalproperties.rb +58 -0
  7. data/lib/json-schema/attributes/allof.rb +39 -0
  8. data/lib/json-schema/attributes/anyof.rb +47 -0
  9. data/lib/json-schema/attributes/dependencies.rb +38 -0
  10. data/lib/json-schema/attributes/dependencies_v4.rb +11 -0
  11. data/lib/json-schema/attributes/disallow.rb +12 -0
  12. data/lib/json-schema/attributes/divisibleby.rb +22 -0
  13. data/lib/json-schema/attributes/enum.rb +24 -0
  14. data/lib/json-schema/attributes/extends.rb +48 -0
  15. data/lib/json-schema/attributes/format.rb +14 -0
  16. data/lib/json-schema/attributes/formats/custom.rb +21 -0
  17. data/lib/json-schema/attributes/formats/date.rb +25 -0
  18. data/lib/json-schema/attributes/formats/date_time.rb +34 -0
  19. data/lib/json-schema/attributes/formats/date_time_v4.rb +15 -0
  20. data/lib/json-schema/attributes/formats/ip.rb +41 -0
  21. data/lib/json-schema/attributes/formats/time.rb +22 -0
  22. data/lib/json-schema/attributes/formats/uri.rb +18 -0
  23. data/lib/json-schema/attributes/items.rb +27 -0
  24. data/lib/json-schema/attributes/limit.rb +52 -0
  25. data/lib/json-schema/attributes/limits/items.rb +15 -0
  26. data/lib/json-schema/attributes/limits/length.rb +15 -0
  27. data/lib/json-schema/attributes/limits/max_items.rb +15 -0
  28. data/lib/json-schema/attributes/limits/max_length.rb +15 -0
  29. data/lib/json-schema/attributes/limits/max_properties.rb +15 -0
  30. data/lib/json-schema/attributes/limits/maximum.rb +15 -0
  31. data/lib/json-schema/attributes/limits/maximum_inclusive.rb +11 -0
  32. data/lib/json-schema/attributes/limits/min_items.rb +15 -0
  33. data/lib/json-schema/attributes/limits/min_length.rb +15 -0
  34. data/lib/json-schema/attributes/limits/min_properties.rb +15 -0
  35. data/lib/json-schema/attributes/limits/minimum.rb +15 -0
  36. data/lib/json-schema/attributes/limits/minimum_inclusive.rb +11 -0
  37. data/lib/json-schema/attributes/limits/numeric.rb +16 -0
  38. data/lib/json-schema/attributes/limits/properties.rb +15 -0
  39. data/lib/json-schema/attributes/maxdecimal.rb +18 -0
  40. data/lib/json-schema/attributes/multipleof.rb +11 -0
  41. data/lib/json-schema/attributes/not.rb +30 -0
  42. data/lib/json-schema/attributes/oneof.rb +56 -0
  43. data/lib/json-schema/attributes/pattern.rb +18 -0
  44. data/lib/json-schema/attributes/patternproperties.rb +22 -0
  45. data/lib/json-schema/attributes/properties.rb +66 -0
  46. data/lib/json-schema/attributes/properties_optional.rb +26 -0
  47. data/lib/json-schema/attributes/properties_v4.rb +13 -0
  48. data/lib/json-schema/attributes/ref.rb +61 -0
  49. data/lib/json-schema/attributes/required.rb +28 -0
  50. data/lib/json-schema/attributes/type.rb +73 -0
  51. data/lib/json-schema/attributes/type_v4.rb +29 -0
  52. data/lib/json-schema/attributes/uniqueitems.rb +20 -0
  53. data/lib/json-schema/errors/custom_format_error.rb +6 -0
  54. data/lib/json-schema/errors/json_load_error.rb +6 -0
  55. data/lib/json-schema/errors/json_parse_error.rb +6 -0
  56. data/lib/json-schema/errors/schema_error.rb +6 -0
  57. data/lib/json-schema/errors/schema_parse_error.rb +8 -0
  58. data/lib/json-schema/errors/uri_error.rb +6 -0
  59. data/lib/json-schema/errors/validation_error.rb +46 -0
  60. data/lib/json-schema/schema/reader.rb +140 -0
  61. data/lib/json-schema/schema/validator.rb +40 -0
  62. data/lib/json-schema/schema.rb +62 -0
  63. data/lib/json-schema/util/array_set.rb +20 -0
  64. data/lib/json-schema/util/uri.rb +110 -0
  65. data/lib/json-schema/util/uuid.rb +284 -0
  66. data/lib/json-schema/validator.rb +609 -0
  67. data/lib/json-schema/validators/draft1.rb +45 -0
  68. data/lib/json-schema/validators/draft2.rb +46 -0
  69. data/lib/json-schema/validators/draft3.rb +50 -0
  70. data/lib/json-schema/validators/draft4.rb +56 -0
  71. data/lib/json-schema/validators/draft6.rb +56 -0
  72. data/lib/json-schema/validators/hyper-draft1.rb +13 -0
  73. data/lib/json-schema/validators/hyper-draft2.rb +13 -0
  74. data/lib/json-schema/validators/hyper-draft3.rb +13 -0
  75. data/lib/json-schema/validators/hyper-draft4.rb +13 -0
  76. data/lib/json-schema/validators/hyper-draft6.rb +13 -0
  77. data/lib/json-schema.rb +18 -0
  78. data/resources/draft-01.json +155 -0
  79. data/resources/draft-02.json +166 -0
  80. data/resources/draft-03.json +174 -0
  81. data/resources/draft-04.json +150 -0
  82. data/resources/draft-06.json +150 -0
  83. metadata +197 -0
@@ -0,0 +1,46 @@
1
+ module JSON
2
+ class Schema
3
+ class ValidationError < StandardError
4
+ INDENT = " "
5
+ attr_accessor :fragments, :schema, :failed_attribute, :sub_errors, :message
6
+
7
+ def initialize(message, fragments, failed_attribute, schema)
8
+ @fragments = fragments.clone
9
+ @schema = schema
10
+ @sub_errors = {}
11
+ @failed_attribute = failed_attribute
12
+ @message = message
13
+ super(message_with_schema)
14
+ end
15
+
16
+ def to_string(subschema_level = 0)
17
+ if @sub_errors.empty?
18
+ subschema_level == 0 ? message_with_schema : message
19
+ else
20
+ messages = ["#{message}. The schema specific errors were:\n"]
21
+ @sub_errors.each do |subschema, errors|
22
+ messages.push "- #{subschema}:"
23
+ messages.concat Array(errors).map { |e| "#{INDENT}- #{e.to_string(subschema_level + 1)}" }
24
+ end
25
+ messages.map { |m| (INDENT * subschema_level) + m }.join("\n")
26
+ end
27
+ end
28
+
29
+ def to_hash
30
+ base = {:schema => @schema.uri, :fragment => ::JSON::Schema::Attribute.build_fragment(fragments), :message => message_with_schema, :failed_attribute => @failed_attribute.to_s.split(":").last.split("Attribute").first}
31
+ if !@sub_errors.empty?
32
+ base[:errors] = @sub_errors.inject({}) do |hsh, (subschema, errors)|
33
+ subschema_sym = subschema.downcase.gsub(/\W+/, '_').to_sym
34
+ hsh[subschema_sym] = Array(errors).map{|e| e.to_hash}
35
+ hsh
36
+ end
37
+ end
38
+ base
39
+ end
40
+
41
+ def message_with_schema
42
+ "#{message} in schema #{schema.uri}"
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,140 @@
1
+ require 'open-uri'
2
+ require 'pathname'
3
+
4
+ module JSON
5
+ class Schema
6
+ # Base for any reading exceptions encountered by {JSON::Schema::Reader}
7
+ class ReadError < StandardError
8
+ # @return [String] the requested schema location which was refused
9
+ attr_reader :location
10
+
11
+ # @return [Symbol] either +:uri+ or +:file+
12
+ attr_reader :type
13
+
14
+ def initialize(location, type)
15
+ @location = location
16
+ @type = type
17
+ super(error_message)
18
+ end
19
+
20
+ private
21
+
22
+ def type_string
23
+ type == :uri ? 'URI' : type.to_s
24
+ end
25
+ end
26
+
27
+ # Raised by {JSON::Schema::Reader} when one of its settings indicate
28
+ # a schema should not be read.
29
+ class ReadRefused < ReadError
30
+ private
31
+ def error_message
32
+ "Read of #{type_string} at #{location} refused"
33
+ end
34
+ end
35
+
36
+ # Raised by {JSON::Schema::Reader} when an attempt to read a schema fails
37
+ class ReadFailed < ReadError
38
+ private
39
+ def error_message
40
+ "Read of #{type_string} at #{location} failed"
41
+ end
42
+ end
43
+
44
+ # When an unregistered schema is encountered, the {JSON::Schema::Reader} is
45
+ # used to fetch its contents and register it with the {JSON::Validator}.
46
+ #
47
+ # This default reader will read schemas from the filesystem or from a URI.
48
+ class Reader
49
+ # The behavior of the schema reader can be controlled by providing
50
+ # callbacks to determine whether to permit reading referenced schemas.
51
+ # The options +accept_uri+ and +accept_file+ should be procs which
52
+ # accept a +URI+ or +Pathname+ object, and return a boolean value
53
+ # indicating whether to read the referenced schema.
54
+ #
55
+ # URIs using the +file+ scheme will be normalized into +Pathname+ objects
56
+ # and passed to the +accept_file+ callback.
57
+ #
58
+ # @param options [Hash]
59
+ # @option options [Boolean, #call] accept_uri (true)
60
+ # @option options [Boolean, #call] accept_file (true)
61
+ #
62
+ # @example Reject all unregistered schemas
63
+ # JSON::Validator.schema_reader = JSON::Schema::Reader.new(
64
+ # :accept_uri => false,
65
+ # :accept_file => false
66
+ # )
67
+ #
68
+ # @example Only permit URIs from certain hosts
69
+ # JSON::Validator.schema_reader = JSON::Schema::Reader.new(
70
+ # :accept_file => false,
71
+ # :accept_uri => proc { |uri| ['mycompany.com', 'json-schema.org'].include?(uri.host) }
72
+ # )
73
+ def initialize(options = {})
74
+ @accept_uri = options.fetch(:accept_uri, true)
75
+ @accept_file = options.fetch(:accept_file, true)
76
+ end
77
+
78
+ # @param location [#to_s] The location from which to read the schema
79
+ # @return [JSON::Schema]
80
+ # @raise [JSON::Schema::ReadRefused] if +accept_uri+ or +accept_file+
81
+ # indicated the schema could not be read
82
+ # @raise [JSON::Schema::ParseError] if the schema was not a valid JSON object
83
+ # @raise [JSON::Schema::ReadFailed] if reading the location was acceptable but the
84
+ # attempt to retrieve it failed
85
+ def read(location)
86
+ uri = JSON::Util::URI.parse(location.to_s)
87
+ body = if uri.scheme.nil? || uri.scheme == 'file'
88
+ uri = JSON::Util::URI.file_uri(uri)
89
+ read_file(Pathname.new(uri.path).expand_path)
90
+ else
91
+ read_uri(uri)
92
+ end
93
+
94
+ JSON::Schema.new(JSON::Validator.parse(body), uri)
95
+ end
96
+
97
+ # @param uri [Addressable::URI]
98
+ # @return [Boolean]
99
+ def accept_uri?(uri)
100
+ if @accept_uri.respond_to?(:call)
101
+ @accept_uri.call(uri)
102
+ else
103
+ @accept_uri
104
+ end
105
+ end
106
+
107
+ # @param pathname [Pathname]
108
+ # @return [Boolean]
109
+ def accept_file?(pathname)
110
+ if @accept_file.respond_to?(:call)
111
+ @accept_file.call(pathname)
112
+ else
113
+ @accept_file
114
+ end
115
+ end
116
+
117
+ private
118
+
119
+ def read_uri(uri)
120
+ if accept_uri?(uri)
121
+ open(uri.to_s).read
122
+ else
123
+ raise JSON::Schema::ReadRefused.new(uri.to_s, :uri)
124
+ end
125
+ rescue OpenURI::HTTPError, SocketError
126
+ raise JSON::Schema::ReadFailed.new(uri.to_s, :uri)
127
+ end
128
+
129
+ def read_file(pathname)
130
+ if accept_file?(pathname)
131
+ File.read(JSON::Util::URI.unescaped_path(pathname.to_s))
132
+ else
133
+ raise JSON::Schema::ReadRefused.new(pathname.to_s, :file)
134
+ end
135
+ rescue Errno::ENOENT
136
+ raise JSON::Schema::ReadFailed.new(pathname.to_s, :file)
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,40 @@
1
+ module JSON
2
+ class Schema
3
+ class Validator
4
+ attr_accessor :attributes, :formats, :uri, :names
5
+ attr_reader :default_formats
6
+
7
+ def initialize()
8
+ @attributes = {}
9
+ @formats = {}
10
+ @default_formats = {}
11
+ @uri = nil
12
+ @names = []
13
+ @metaschema_name = ''
14
+ end
15
+
16
+ def extend_schema_definition(schema_uri)
17
+ warn "[DEPRECATION NOTICE] The preferred way to extend a Validator is by subclassing, rather than #extend_schema_definition. This method will be removed in version >= 3."
18
+ validator = JSON::Validator.validator_for_uri(schema_uri)
19
+ @attributes.merge!(validator.attributes)
20
+ end
21
+
22
+ def validate(current_schema, data, fragments, processor, options = {})
23
+ current_schema.schema.each do |attr_name,attribute|
24
+ # Added: don't try to validate if
25
+ # - Attribute is false and does not needs to validate anything (eg: uniqueItems) some still needs validation (eg: additionalProperties)
26
+ # - Attribute is nil and it makes crash the validation (eg: divisibleBy). This should fixed on mountapi side
27
+ if @attributes.has_key?(attr_name.to_s) && (attribute != false || @attributes[attr_name.to_s].validate_on_false?) && !attribute.nil?
28
+ @attributes[attr_name.to_s].validate(current_schema, data, fragments, processor, self, options)
29
+ end
30
+ end
31
+ data
32
+ end
33
+
34
+ def metaschema
35
+ resources = File.expand_path('../../../../resources', __FILE__)
36
+ File.join(resources, @metaschema_name)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,62 @@
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
+ # If there is an ID on this schema, use it to generate the URI
13
+ if @schema['id'] && @schema['id'].kind_of?(String)
14
+ temp_uri = JSON::Util::URI.parse(@schema['id'])
15
+ if temp_uri.relative?
16
+ temp_uri = uri.join(temp_uri)
17
+ end
18
+ @uri = temp_uri
19
+ end
20
+ @uri = JSON::Util::URI.strip_fragment(@uri)
21
+
22
+ # If there is a $schema on this schema, use it to determine which validator to use
23
+ if @schema['$schema']
24
+ @validator = JSON::Validator.validator_for_uri(@schema['$schema'])
25
+ elsif parent_validator
26
+ @validator = parent_validator
27
+ else
28
+ @validator = JSON::Validator.default_validator
29
+ end
30
+ end
31
+
32
+ def validate(data, fragments, processor, options = {})
33
+ @validator.validate(self, data, fragments, processor, options)
34
+ end
35
+
36
+ def self.stringify(schema)
37
+ case schema
38
+ when Hash then
39
+ Hash[schema.map { |key, value| [key.to_s, stringify(schema[key])] }]
40
+ when Array then
41
+ schema.map do |schema_item|
42
+ stringify(schema_item)
43
+ end
44
+ when Symbol then
45
+ schema.to_s
46
+ else
47
+ schema
48
+ end
49
+ end
50
+
51
+ # @return [JSON::Schema] a new schema matching an array whose items all match this schema.
52
+ def to_array_schema
53
+ array_schema = { 'type' => 'array', 'items' => schema }
54
+ array_schema['$schema'] = schema['$schema'] unless schema['$schema'].nil?
55
+ self.class.new(array_schema, uri, validator)
56
+ end
57
+
58
+ def to_s
59
+ @schema.to_json
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,20 @@
1
+ require 'set'
2
+
3
+ # This is a hack that I don't want to ever use anywhere else or repeat EVER, but we need enums to be
4
+ # an Array to pass schema validation. But we also want fast lookup!
5
+
6
+ class ArraySet < Array
7
+ def include?(obj)
8
+ if !defined? @values
9
+ @values = Set.new
10
+ self.each { |x| @values << convert_to_float_if_numeric(x) }
11
+ end
12
+ @values.include?(convert_to_float_if_numeric(obj))
13
+ end
14
+
15
+ private
16
+
17
+ def convert_to_float_if_numeric(value)
18
+ value.is_a?(Numeric) ? value.to_f : value
19
+ end
20
+ end
@@ -0,0 +1,110 @@
1
+ require 'addressable/uri'
2
+
3
+ module JSON
4
+ module Util
5
+ module URI
6
+ SUPPORTED_PROTOCOLS = %w(http https ftp tftp sftp ssh svn+ssh telnet nntp gopher wais ldap prospero)
7
+
8
+ def self.normalized_uri(uri, base_path = Dir.pwd)
9
+ @normalize_cache ||= {}
10
+ normalized_uri = @normalize_cache[uri]
11
+
12
+ if !normalized_uri
13
+ normalized_uri = parse(uri)
14
+ # Check for absolute path
15
+ if normalized_uri.relative?
16
+ data = normalized_uri
17
+ data = File.join(base_path, data) if data.path[0,1] != "/"
18
+ normalized_uri = file_uri(data)
19
+ end
20
+ @normalize_cache[uri] = normalized_uri.freeze
21
+ end
22
+
23
+ normalized_uri
24
+ end
25
+
26
+ def self.absolutize_ref(ref, base)
27
+ ref_uri = strip_fragment(ref.dup)
28
+
29
+ return ref_uri if ref_uri.absolute?
30
+ return parse(base) if ref_uri.path.empty?
31
+
32
+ uri = strip_fragment(base.dup).join(ref_uri.path)
33
+ normalized_uri(uri)
34
+ end
35
+
36
+ def self.normalize_ref(ref, base)
37
+ ref_uri = parse(ref)
38
+ base_uri = parse(base)
39
+
40
+ ref_uri.defer_validation do
41
+ if ref_uri.relative?
42
+ ref_uri.merge!(base_uri)
43
+
44
+ # Check for absolute path
45
+ path, fragment = ref.to_s.split("#")
46
+ if path.nil? || path == ''
47
+ ref_uri.path = base_uri.path
48
+ elsif path[0,1] == "/"
49
+ ref_uri.path = Pathname.new(path).cleanpath.to_s
50
+ else
51
+ ref_uri.join!(path)
52
+ end
53
+
54
+ ref_uri.fragment = fragment
55
+ end
56
+
57
+ ref_uri.fragment = "" if ref_uri.fragment.nil? || ref_uri.fragment.empty?
58
+ end
59
+
60
+ ref_uri
61
+ end
62
+
63
+ def self.parse(uri)
64
+ if uri.is_a?(Addressable::URI)
65
+ return uri.dup
66
+ else
67
+ @parse_cache ||= {}
68
+ parsed_uri = @parse_cache[uri]
69
+ if parsed_uri
70
+ parsed_uri.dup
71
+ else
72
+ @parse_cache[uri] = Addressable::URI.parse(uri)
73
+ end
74
+ end
75
+ rescue Addressable::URI::InvalidURIError => e
76
+ raise JSON::Schema::UriError.new(e.message)
77
+ end
78
+
79
+ def self.strip_fragment(uri)
80
+ parsed_uri = parse(uri)
81
+ if parsed_uri.fragment.nil? || parsed_uri.fragment.empty?
82
+ parsed_uri
83
+ else
84
+ parsed_uri.merge(:fragment => "")
85
+ end
86
+ end
87
+
88
+ def self.file_uri(uri)
89
+ parsed_uri = parse(uri)
90
+
91
+ Addressable::URI.convert_path(parsed_uri.path)
92
+ end
93
+
94
+ def self.unescape_uri(uri)
95
+ Addressable::URI.unescape(uri)
96
+ end
97
+
98
+ def self.unescaped_path(uri)
99
+ parsed_uri = parse(uri)
100
+
101
+ Addressable::URI.unescape(parsed_uri.path)
102
+ end
103
+
104
+ def self.clear_cache
105
+ @parse_cache = {}
106
+ @normalize_cache = {}
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,284 @@
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
+ def to_int
245
+ tmp = self.raw_bytes.unpack "C*"
246
+ tmp.inject do |r, i|
247
+ r * 256 | i
248
+ end
249
+ end
250
+ alias to_i to_int
251
+
252
+ # Gets the version of this UUID
253
+ # returns nil if bad version
254
+ def version
255
+ a = unpack
256
+ v = (a[2] & 0xF000).to_s(16)[0].chr.to_i
257
+ return v if (1..5).include? v
258
+ return nil
259
+ end
260
+
261
+ # Two UUIDs are said to be equal if and only if their (byte-order
262
+ # canonicalized) integer representations are equivallent. Refer RFC4122 for
263
+ # details.
264
+ def == other
265
+ to_i == other.to_i
266
+ end
267
+
268
+ include Comparable
269
+ # UUIDs are comparable (don't know what benefits are there, though).
270
+ def <=> other
271
+ to_s <=> other.to_s
272
+ end
273
+
274
+ # Pre-defined UUID Namespaces described in RFC4122 Appendix C.
275
+ NameSpace_DNS = parse "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
276
+ NameSpace_URL = parse "6ba7b811-9dad-11d1-80b4-00c04fd430c8"
277
+ NameSpace_OID = parse "6ba7b812-9dad-11d1-80b4-00c04fd430c8"
278
+ NameSpace_X500 = parse "6ba7b814-9dad-11d1-80b4-00c04fd430c8"
279
+
280
+ # The Nil UUID in RFC4122 Section 4.1.7
281
+ Nil = parse "00000000-0000-0000-0000-000000000000"
282
+ end
283
+ end
284
+ end