{"record":{"id":"10c6cb4e85986168","repo":"puppetlabs/puppet","slug":"unable-to-parse-simple-as-a-version-range-ide","errorCode":null,"errorMessage":"Unable to parse '#{simple}' as a version range identifier","messagePattern":"Unable to parse '#(.+?)' as a version range identifier","errorType":"validation","errorClass":"Puppet::Util::Package::Version::Range::ValidationFailure","httpStatus":null,"severity":"error","filePath":"lib/puppet/util/package/version/range.rb","lineNumber":37,"sourceCode":"    #   * ex. `\"1.0.0\"`, `\"1.2.3-pre\"`\n    # * Inequalities\n    #   * ex. `\">1.0.0\"`, `\"<3.2.0\"`, `\">=4.0.0\"`\n    # * Range Intersections (min is always first)\n    #   * ex. `\">1.0.0 <=2.3.0\"`\n    #\n    RANGE_SPLIT = /\\s+/\n    FULL_REGEX = /\\A((?:[<>=])*)(.+)\\Z/\n\n    # @param range_string [String] the version range string to parse\n    # @param version_class [Version] a version class implementing comparison operators and parse method\n    # @return [Range] a new {Range} instance\n    # @api public\n    def self.parse(range_string, version_class)\n      raise ValidationFailure, \"Unable to parse '#{range_string}' as a string\" unless range_string.is_a?(String)\n\n      simples = range_string.split(RANGE_SPLIT).map do |simple|\n        match, operator, version = *simple.match(FULL_REGEX)\n        raise ValidationFailure, \"Unable to parse '#{simple}' as a version range identifier\" unless match\n\n        case operator\n        when '>'\n          Gt.new(version_class.parse(version))\n        when '>='\n          GtEq.new(version_class.parse(version))\n        when '<'\n          Lt.new(version_class.parse(version))\n        when '<='\n          LtEq.new(version_class.parse(version))\n        when ''\n          Eq.new(version_class.parse(version))\n        else\n          raise ValidationFailure, \"Operator '#{operator}' is not implemented\"\n        end\n      end\n      simples.size == 1 ? simples[0] : MinMax.new(simples[0], simples[1])\n    end","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/puppetlabs/puppet/blob/e227c27540975c25aa22d533a52424a9d2fc886a/lib/puppet/util/package/version/range.rb#L19-L55","documentation":"Inside Range.parse (range.rb:37): each whitespace-separated token must match FULL_REGEX = /\\A((?:[<>=])*)(.+)\\Z/. The capture group (.+) requires at least one version character after the operator run, so a bare operator token (no version attached) fails to match and raises ValidationFailure 'Unable to parse ... as a version range identifier'. Because tokens are split on /\\s+/, writing a space between operator and version turns the operator into its own token and hits exactly this raise.","triggerScenarios":"Range.parse('> 1.0', klass) splits into '>' and '1.0'; the token '>' has an empty (.+) part so match is nil and line 37 raises. Also '>= 2.0 <= 3.0' (both operators detached), or any token made only of [<>=] characters.","commonSituations":"Version constraints authored for readability as '>= 1.2.0' (with a space) in manifests or Hiera data; constraints copied from Gemfile/pip syntax that assumes space-tolerant parsing; templates interpolating \"#{op} #{ver}\" with an unintended space.","solutions":["Remove the space between operator and version: '>1.0', '>=2.0', '<=3.2.0'.","Normalize before parsing: squeeze each operator token onto its version (e.g. str.gsub(/([<>=]+)\\s+/, '\\1')).","Pre-validate each token against /\\A(?:[<>=]*)(.+)\\Z/ plus a non-empty operator when an operator was intended.","Rescue Range::ValidationFailure and report the offending token to the user."],"exampleFix":"// before\nrng = Puppet::Util::Package::Version::Range.parse(constraint, version_class) # constraint = '>= 1.2.0'\n\n// after\nnormalized = constraint.strip.gsub(/([<>=]+)\\s+/, '\\1')\nrng = Puppet::Util::Package::Version::Range.parse(normalized, version_class)","handlingStrategy":"validation","validationCode":"def range_tokens_ok?(str)\n  str.is_a?(String) && str.split(/\\s+/).all? { |t| t.match?(/\\A(?:[<>=]*)(.+\\z)/m) && !t.match?(/\\A[<>=]+\\z/) }\nend","typeGuard":null,"tryCatchPattern":"begin\n  Range.parse(normalized, version_class)\nrescue Puppet::Util::Package::Version::Range::ValidationFailure => e\n  raise ArgumentError, \"bad range #{str.inspect}: #{e.message}\"\nend","preventionTips":["Normalize '> 1.0' to '>1.0' with gsub(/([<>=]+)\\s+/, '\\1') before parsing.","Document the no-space operator format wherever constraints are user-editable (Hiera keys, module READMEs)."],"tags":["puppet","version-range","package-version","regex","validation"],"backgroundTag":"version-range-parse-failed","analyzedSha":"e227c27540975c25aa22d533a52424a9d2fc886a","analyzedAt":"2026-08-21T20:49:46.650Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}