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.
- checksums.yaml +7 -0
- data/LICENSE.md +19 -0
- data/README.md +496 -0
- data/lib/json-schema/attribute.rb +57 -0
- data/lib/json-schema/attributes/additionalitems.rb +28 -0
- data/lib/json-schema/attributes/additionalproperties.rb +58 -0
- data/lib/json-schema/attributes/allof.rb +39 -0
- data/lib/json-schema/attributes/anyof.rb +47 -0
- data/lib/json-schema/attributes/dependencies.rb +38 -0
- data/lib/json-schema/attributes/dependencies_v4.rb +11 -0
- data/lib/json-schema/attributes/disallow.rb +12 -0
- data/lib/json-schema/attributes/divisibleby.rb +22 -0
- data/lib/json-schema/attributes/enum.rb +24 -0
- data/lib/json-schema/attributes/extends.rb +48 -0
- data/lib/json-schema/attributes/format.rb +14 -0
- data/lib/json-schema/attributes/formats/custom.rb +21 -0
- data/lib/json-schema/attributes/formats/date.rb +25 -0
- data/lib/json-schema/attributes/formats/date_time.rb +34 -0
- data/lib/json-schema/attributes/formats/date_time_v4.rb +15 -0
- data/lib/json-schema/attributes/formats/ip.rb +41 -0
- data/lib/json-schema/attributes/formats/time.rb +22 -0
- data/lib/json-schema/attributes/formats/uri.rb +18 -0
- data/lib/json-schema/attributes/items.rb +27 -0
- data/lib/json-schema/attributes/limit.rb +52 -0
- data/lib/json-schema/attributes/limits/items.rb +15 -0
- data/lib/json-schema/attributes/limits/length.rb +15 -0
- data/lib/json-schema/attributes/limits/max_items.rb +15 -0
- data/lib/json-schema/attributes/limits/max_length.rb +15 -0
- data/lib/json-schema/attributes/limits/max_properties.rb +15 -0
- data/lib/json-schema/attributes/limits/maximum.rb +15 -0
- data/lib/json-schema/attributes/limits/maximum_inclusive.rb +11 -0
- data/lib/json-schema/attributes/limits/min_items.rb +15 -0
- data/lib/json-schema/attributes/limits/min_length.rb +15 -0
- data/lib/json-schema/attributes/limits/min_properties.rb +15 -0
- data/lib/json-schema/attributes/limits/minimum.rb +15 -0
- data/lib/json-schema/attributes/limits/minimum_inclusive.rb +11 -0
- data/lib/json-schema/attributes/limits/numeric.rb +16 -0
- data/lib/json-schema/attributes/limits/properties.rb +15 -0
- data/lib/json-schema/attributes/maxdecimal.rb +18 -0
- data/lib/json-schema/attributes/multipleof.rb +11 -0
- data/lib/json-schema/attributes/not.rb +30 -0
- data/lib/json-schema/attributes/oneof.rb +56 -0
- data/lib/json-schema/attributes/pattern.rb +18 -0
- data/lib/json-schema/attributes/patternproperties.rb +22 -0
- data/lib/json-schema/attributes/properties.rb +66 -0
- data/lib/json-schema/attributes/properties_optional.rb +26 -0
- data/lib/json-schema/attributes/properties_v4.rb +13 -0
- data/lib/json-schema/attributes/ref.rb +61 -0
- data/lib/json-schema/attributes/required.rb +28 -0
- data/lib/json-schema/attributes/type.rb +73 -0
- data/lib/json-schema/attributes/type_v4.rb +29 -0
- data/lib/json-schema/attributes/uniqueitems.rb +20 -0
- data/lib/json-schema/errors/custom_format_error.rb +6 -0
- data/lib/json-schema/errors/json_load_error.rb +6 -0
- data/lib/json-schema/errors/json_parse_error.rb +6 -0
- data/lib/json-schema/errors/schema_error.rb +6 -0
- data/lib/json-schema/errors/schema_parse_error.rb +8 -0
- data/lib/json-schema/errors/uri_error.rb +6 -0
- data/lib/json-schema/errors/validation_error.rb +46 -0
- data/lib/json-schema/schema/reader.rb +140 -0
- data/lib/json-schema/schema/validator.rb +40 -0
- data/lib/json-schema/schema.rb +62 -0
- data/lib/json-schema/util/array_set.rb +20 -0
- data/lib/json-schema/util/uri.rb +110 -0
- data/lib/json-schema/util/uuid.rb +284 -0
- data/lib/json-schema/validator.rb +609 -0
- data/lib/json-schema/validators/draft1.rb +45 -0
- data/lib/json-schema/validators/draft2.rb +46 -0
- data/lib/json-schema/validators/draft3.rb +50 -0
- data/lib/json-schema/validators/draft4.rb +56 -0
- data/lib/json-schema/validators/draft6.rb +56 -0
- data/lib/json-schema/validators/hyper-draft1.rb +13 -0
- data/lib/json-schema/validators/hyper-draft2.rb +13 -0
- data/lib/json-schema/validators/hyper-draft3.rb +13 -0
- data/lib/json-schema/validators/hyper-draft4.rb +13 -0
- data/lib/json-schema/validators/hyper-draft6.rb +13 -0
- data/lib/json-schema.rb +18 -0
- data/resources/draft-01.json +155 -0
- data/resources/draft-02.json +166 -0
- data/resources/draft-03.json +174 -0
- data/resources/draft-04.json +150 -0
- data/resources/draft-06.json +150 -0
- 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
|