lib/uuid/ncname.rb in uuid-ncname-0.2.5 vs lib/uuid/ncname.rb in uuid-ncname-0.2.6

- old
+ new

@@ -114,12 +114,13 @@ list.pack 'N4' }, ], ] - def self.encode_version version - ((version & 15) + 65).chr + def self.encode_version version, radix + offset = radix == 32 ? 97 : 65 + ((version & 15) + offset).chr end def self.decode_version version (version.upcase.ord - 65) % 16 end @@ -135,10 +136,31 @@ version end public + # This error gets thrown when a UUID-NCName token can't be + # positively determined to be one version or the other. + class AmbiguousToken < ArgumentError + + # @return [String] The ambiguous token + attr_reader :token + # @return [String] The UUID when decoded using version 0 + attr_reader :v0 + # @return [String] The UUID when decoded using version 1 + attr_reader :v1 + + # @param token [#to_s] The token in question + # @param v0 [#to_s] UUID decoded with decoding scheme version 0 + # @param v1 [#to_s] UUID decoded with decoding scheme version 1 + + def initialize token, v0: nil, v1: nil + @v0 = v0 || from_ncname(token, version: 0) + @v1 = v1 || from_ncname(token, version: 1) + end + end + # Converts a UUID (or object that when converted to a string looks # like a UUID) to an NCName. By default it produces the Base64 # variant. # # @param uuid [#to_s] whatever it is, it had better look like a @@ -146,11 +168,11 @@ # 16-byte binary strings, etc. # # @param radix [32, 64] either the number 32 or the number 64. # # @param version [0, 1] An optional formatting version, where 0 is - # the naïve original version and 1 moves the `variant` nybble out + # the naïve original version and 1 moves the +variant+ nybble out # to the end of the identifier. You will be warned for the time # being if you do not set this parameter explicitly. The default # version is 1. # # @param align [true, false] Optional directive to treat the @@ -158,21 +180,22 @@ # representation. Since the version nybble is removed from the # string and the first 120 bits divide evenly into both Base32 and # Base64, the overhang is only ever 4 bits. This means that when # the terminating character is aligned, it will always be in the # range of the letters A through P in (the RFC 3548/4648 - # representations of) both Base32 and Base64. When `version` is 1 + # representations of) both Base32 and Base64. When +version+ is 1 # and the terminating character is aligned, RFC4122-compliant UUIDs - # will always terminate with I, J, K, or L. Defaults to `true`. + # will always terminate with +I+, +J+, +K+, or +L+. Defaults to + # +true+. # # @return [String] The NCName-formatted UUID. def self.to_ncname uuid, radix: 64, version: nil, align: true raise 'Radix must be either 32 or 64' unless [32, 64].include? radix raise 'UUID must be something stringable' if uuid.nil? or not uuid.respond_to? :to_s - raise 'Align must be true or false' unless [true, false].include? align + align = !!align # coerce to a boolean # XXX remove this when appropriate version = warn_version(version) uuid = uuid.to_s @@ -193,13 +216,13 @@ end raise 'Binary representation of UUID is shorter than 16 bytes' if bin.length < 16 - uuidver, content = TRANSFORM[version][0].call bin[0, 16] + uuidver, content = TRANSFORM[version].first.call bin[0, 16] - encode_version(uuidver) + ENCODE[radix].call(content, align) + encode_version(uuidver, radix) + ENCODE[radix].call(content, align) end # Converts an NCName-encoded UUID back to its canonical # representation. Will return nil if the input doesn't match the # radix (if supplied) or is otherwise malformed. @@ -208,20 +231,20 @@ # 22-character (Base64) variant, or a 26-character (Base32) variant. # # @param radix [nil, 32, 64] Optional radix; will use heuristic if omitted. # # @param format [:str, :hex, :b64, :bin] An optional formatting - # parameter; defaults to `:str`, the canonical string representation. + # parameter; defaults to +:str+, the canonical string representation. # # @param version [0, 1] See ::to_ncname. Defaults to 1. # # @param align [nil, true, false] See ::to_ncname for details. - # Setting this parameter to `nil`, the default, will cause the + # Setting this parameter to +nil+, the default, will cause the # decoder to detect the alignment state from the identifier. # - # @param validate [false, true] Check that the ninth octet is - # correctly masked _after_ decoding. + # @param validate [false, true] Check that the ninth (the variant) + # octet is correctly masked _after_ decoding. # # @return [String, nil] The corresponding UUID or nil if the input # is malformed. def self.from_ncname ncname, @@ -335,36 +358,42 @@ # Test if the given token is a UUID NCName, with a hint to its # version. This method can positively identify a token as a UUID # NCName, but there is a small subset of UUIDs which will produce # tokens which are valid in both versions. The method returns - # `false` if the token is invalid, otherwise it returns `0` or `1` + # +false+ if the token is invalid, otherwise it returns +0+ or +1+ # for the guessed version. # - # @note Version 1 tokens always end with I, J, K, or L (with base32 - # being case-insensitive), so tokens that end in something else will - # be version 0. + # @note Version 1 tokens always end with +I+, +J+, +K+, or +L+ (with + # base32 being case-insensitive), so tokens that end in something + # else will always be version 0. # # @param token [#to_s] The token to test # + # @param strict [false, true] + # # @return [false, 0, 1] - def self.valid? token + def self.valid? token, strict: false token = token.to_s if /^[A-Pa-p](?:[0-9A-Za-z_-]{21}|[2-7A-Za-z]{25})$/.match token # false is definitely version zero but true is only maybe version 1 version = /^(?:.{21}[I-L]|.{25}[I-Li-l])$/.match(token) ? 1 : 0 # try decoding with validation on uu = from_ncname token, version: version, validate: true - if version == 1 and !uu - # try version zero - uu = from_ncname token, version: 0, validate: true - # either zero or invalid - uu ? 0 : false - else - version + # note that version 1 will always return something because the + # method of detecting it is a version 1 also happens to be the + # method of determining whether or not it is valid. + return false unless uu + + if version == 1 and strict + # but we can also check if the input is a valid version 0 + u0 = from_ncname token, version: 0, validate: true + raise AmbiguousToken.new(token, v0: u0, v1: uu) if u0 end + + version else false end end