openapi-sourcetools 0.8.1 → 0.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 +4 -4
- data/bin/openapi-patterntests +159 -0
- data/lib/openapi/sourcetools/apiobjects.rb +64 -0
- data/lib/openapi/sourcetools/helper.rb +41 -0
- data/lib/openapi/sourcetools/output.rb +3 -1
- data/lib/openapi/sourcetools/version.rb +1 -1
- metadata +4 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 44ff7d64b4781fdfbab07060f6faa294a8092e342530e6cde786e50ff8f69db1
         | 
| 4 | 
            +
              data.tar.gz: 893e593407c4734e67c83bffe77a67bb55b2ecef3970620a416580fd33109437
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 741c12063a6d5df5d090f26b80c60a7810083537076573cb10f9ba5a3ca3afc8109ab6faa305707139c881a7efa1f478d7d0de562db67b44f93c0329ae8263e4
         | 
| 7 | 
            +
              data.tar.gz: 9e1c2bc87442258d75517beb13f99c1e0b210b7074ee622db8057e1e4ebf17c253538ba52b893b0f148baa21188a306976a9783d778e92dd769caee6eb84aef6
         | 
| @@ -0,0 +1,159 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            # Copyright © 2025 Ismo Kärkkäinen
         | 
| 5 | 
            +
            # Licensed under Universal Permissive License. See LICENSE.txt.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            require_relative '../lib/openapi/sourcetools/common'
         | 
| 8 | 
            +
            require 'optparse'
         | 
| 9 | 
            +
            include OpenAPISourceTools
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            def key(item)
         | 
| 12 | 
            +
              "#{item['pattern']}::#{item.fetch('minLength', 0)}::#{item.fetch('maxLength', 'inf')}"
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            def find_patterns(doc, pmm)
         | 
| 16 | 
            +
              if doc.is_a?(Hash)
         | 
| 17 | 
            +
                if doc.key?('pattern')
         | 
| 18 | 
            +
                  item = { 'pattern' => doc['pattern'] }
         | 
| 19 | 
            +
                  item['minLength'] = doc['minLength'] if doc.key?('minLength')
         | 
| 20 | 
            +
                  item['maxLength'] = doc['maxLength'] if doc.key?('maxLength')
         | 
| 21 | 
            +
                  pmm[key(doc)] = item
         | 
| 22 | 
            +
                  return
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
                doc = doc.values
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
              doc.each { |v| find_patterns(v, pmm) } if doc.is_a?(Array)
         | 
| 27 | 
            +
            end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            def pattern_list2hash(list)
         | 
| 30 | 
            +
              pmms = {}
         | 
| 31 | 
            +
              list.each { |item| pmms[key(item)] = item }
         | 
| 32 | 
            +
              pmms
         | 
| 33 | 
            +
            end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            def add_strings(item)
         | 
| 36 | 
            +
              passes = []
         | 
| 37 | 
            +
              fails = []
         | 
| 38 | 
            +
              min_len = item.fetch('minLength', 0)
         | 
| 39 | 
            +
              fails.push('f' * (min_len - 1)) unless min_len.zero?
         | 
| 40 | 
            +
              max_len = item['maxLength']
         | 
| 41 | 
            +
              fails.push('f' * (max_len + 1)) unless max_len.nil?
         | 
| 42 | 
            +
              # Fails within length limits require considering the pattern.
         | 
| 43 | 
            +
              # All passes require considering the pattern.
         | 
| 44 | 
            +
              item['pass'] = passes
         | 
| 45 | 
            +
              item['fail'] = fails
         | 
| 46 | 
            +
            end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            def merge_arrays(current, past)
         | 
| 49 | 
            +
              if current.is_a?(Array)
         | 
| 50 | 
            +
                if past.is_a?(Array)
         | 
| 51 | 
            +
                  return current.concat(past)
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
                current
         | 
| 54 | 
            +
              elsif past.is_a?(Array)
         | 
| 55 | 
            +
                past
         | 
| 56 | 
            +
              else
         | 
| 57 | 
            +
                false
         | 
| 58 | 
            +
              end
         | 
| 59 | 
            +
            end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            def merge_existing(pmms, existing)
         | 
| 62 | 
            +
              pmms.each do |k, v|
         | 
| 63 | 
            +
                ex = existing[k]
         | 
| 64 | 
            +
                next if ex.nil?
         | 
| 65 | 
            +
                %w[pass fail].each do |arr|
         | 
| 66 | 
            +
                  m = merge_arrays(v[arr], ex[arr])
         | 
| 67 | 
            +
                  v[arr] = m.is_a?(Array) ? m.sort!.uniq : m
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
                ex.each do |ek, ev|
         | 
| 70 | 
            +
                  v[ek] = ev unless v.key?(ek)
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
              end
         | 
| 73 | 
            +
            end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            def add_removed(pmms, existing)
         | 
| 76 | 
            +
              existing.each { |k, v| pmms[k] = v unless pmms.key?(k) }
         | 
| 77 | 
            +
            end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            def compare(a, b)
         | 
| 80 | 
            +
              d = a['pattern'] <=> b['pattern']
         | 
| 81 | 
            +
              return d unless d.zero?
         | 
| 82 | 
            +
              d = a.fetch('minLength', 0) <=> b.fetch('minLength', 0)
         | 
| 83 | 
            +
              return d unless d.zero?
         | 
| 84 | 
            +
              a.fetch('maxLength', Float::INFINITY) <=> b.fetch('maxLength', Float::INFINITY)
         | 
| 85 | 
            +
            end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            def pattern_hash2list(pmms)
         | 
| 88 | 
            +
              pmms.values.sort { |a, b| compare(a, b) }
         | 
| 89 | 
            +
            end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
            def main
         | 
| 92 | 
            +
              array_name = 'patterns'
         | 
| 93 | 
            +
              input_name = nil
         | 
| 94 | 
            +
              output_name = nil
         | 
| 95 | 
            +
              source_tests = nil
         | 
| 96 | 
            +
              keep = false
         | 
| 97 | 
            +
              chain = []
         | 
| 98 | 
            +
             | 
| 99 | 
            +
              parser = OptionParser.new do |opts|
         | 
| 100 | 
            +
                opts.summary_indent = '  '
         | 
| 101 | 
            +
                opts.summary_width = 20
         | 
| 102 | 
            +
                opts.banner = 'Usage: openapi-patterntests [options]'
         | 
| 103 | 
            +
                opts.separator ''
         | 
| 104 | 
            +
                opts.separator 'Options:'
         | 
| 105 | 
            +
                opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
         | 
| 106 | 
            +
                  input_name = f
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
                opts.on('-o', '--output FILE', 'Output to FILE, not stdout.') do |f|
         | 
| 109 | 
            +
                  output_name = f
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
                opts.on('-t', '--tests FILE', 'Read existing tests from FILE.') do |f|
         | 
| 112 | 
            +
                  source_tests = f
         | 
| 113 | 
            +
                end
         | 
| 114 | 
            +
                opts.on('-u', '--under STR', %(Top-level "#{array_name}" is under dot-separated keys.)) do |s|
         | 
| 115 | 
            +
                  chain = s.split('.').reject(&:empty?)
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
                opts.on('-k', '--[no-]keep', "Keep missing test patterns, default = #{Common.yesno(keep)}.") do |b|
         | 
| 118 | 
            +
                  keep = b
         | 
| 119 | 
            +
                end
         | 
| 120 | 
            +
                opts.on('-h', '--help', 'Print this help and exit.') do
         | 
| 121 | 
            +
                  $stdout.puts %(#{opts}
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            Loads API document in OpenAPI format, extracts string patterns, and outputs
         | 
| 124 | 
            +
            a YAML file that contains mapping from patterns to matching and not mathcing
         | 
| 125 | 
            +
            strings for testing generated code.
         | 
| 126 | 
            +
            )
         | 
| 127 | 
            +
                  exit 0
         | 
| 128 | 
            +
                end
         | 
| 129 | 
            +
              end
         | 
| 130 | 
            +
              parser.order!
         | 
| 131 | 
            +
             | 
| 132 | 
            +
              doc = Common.load_source(input_name)
         | 
| 133 | 
            +
              return 2 if doc.nil?
         | 
| 134 | 
            +
             | 
| 135 | 
            +
              if source_tests.nil?
         | 
| 136 | 
            +
                ex = {}
         | 
| 137 | 
            +
                pats = []
         | 
| 138 | 
            +
              else
         | 
| 139 | 
            +
                ex = Common.load_source(source_tests)
         | 
| 140 | 
            +
                return 2 if ex.nil?
         | 
| 141 | 
            +
                parent = chain.empty? ? ex : ex.dig(*chain)
         | 
| 142 | 
            +
                return Common.aargh("Key chain #{chain.join('.')} not found in source tests.", 4) if parent.nil?
         | 
| 143 | 
            +
                pats = parent[array_name] || []
         | 
| 144 | 
            +
              end
         | 
| 145 | 
            +
              chain.push(array_name)
         | 
| 146 | 
            +
             | 
| 147 | 
            +
              existing = pattern_list2hash(pats)
         | 
| 148 | 
            +
              pmms = {}
         | 
| 149 | 
            +
              find_patterns(doc, pmms)
         | 
| 150 | 
            +
              pmms.each_value { |item| add_strings(item) }
         | 
| 151 | 
            +
              merge_existing(pmms, existing)
         | 
| 152 | 
            +
              add_removed(pmms, existing) if keep
         | 
| 153 | 
            +
              pats = pattern_hash2list(pmms)
         | 
| 154 | 
            +
             | 
| 155 | 
            +
              Common.bury(ex, chain, pats)
         | 
| 156 | 
            +
              Common.dump_result(output_name, ex, 3)
         | 
| 157 | 
            +
            end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
            exit(main) if defined?($unit_test).nil?
         | 
| @@ -187,5 +187,69 @@ module OpenAPISourceTools | |
| 187 187 | 
             
                  end
         | 
| 188 188 | 
             
                  out
         | 
| 189 189 | 
             
                end
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                # Single server variable object.
         | 
| 192 | 
            +
                class ServerVariableObject
         | 
| 193 | 
            +
                  include Comparable
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                  attr_reader :name, :default, :enum
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                  def initialize(name, variable_object)
         | 
| 198 | 
            +
                    @name = name
         | 
| 199 | 
            +
                    @default = variable_object['default']
         | 
| 200 | 
            +
                    @enum = (variable_object['enum'] || []).sort!
         | 
| 201 | 
            +
                  end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                  def <=>(other)
         | 
| 204 | 
            +
                    d = @name <=> other.name
         | 
| 205 | 
            +
                    return d unless d.zero?
         | 
| 206 | 
            +
                    d = @default <=> other.default
         | 
| 207 | 
            +
                    return d unless d.zero?
         | 
| 208 | 
            +
                    @enum <=> other.enum
         | 
| 209 | 
            +
                  end
         | 
| 210 | 
            +
                end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                # Single server object with variables.
         | 
| 213 | 
            +
                class ServerObject
         | 
| 214 | 
            +
                  include Comparable
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                  attr_reader :url, :variables
         | 
| 217 | 
            +
             | 
| 218 | 
            +
                  def initialize(server_object)
         | 
| 219 | 
            +
                    @url = server_object['url']
         | 
| 220 | 
            +
                    vs = server_object['variables'] || {}
         | 
| 221 | 
            +
                    @variables = vs.keys.sort!.map do |name|
         | 
| 222 | 
            +
                      obj = vs[name]
         | 
| 223 | 
            +
                      ServerVariableObject.new(name, obj)
         | 
| 224 | 
            +
                    end
         | 
| 225 | 
            +
                  end
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                  def <=>(other)
         | 
| 228 | 
            +
                    d = @url <=> other.url
         | 
| 229 | 
            +
                    return d unless d.zero?
         | 
| 230 | 
            +
                    if @variables.nil? || other.variables.nil?
         | 
| 231 | 
            +
                      return -1 if @variables.nil?
         | 
| 232 | 
            +
                      return 1 if other.variables.nil?
         | 
| 233 | 
            +
                    end
         | 
| 234 | 
            +
                    @variables <=> other.variables
         | 
| 235 | 
            +
                  end
         | 
| 236 | 
            +
                end
         | 
| 237 | 
            +
             | 
| 238 | 
            +
                # Combines servers array with set identifier.
         | 
| 239 | 
            +
                class ServerAlternatives
         | 
| 240 | 
            +
                  include Comparable
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                  attr_reader :servers
         | 
| 243 | 
            +
                  attr_accessor :set_id
         | 
| 244 | 
            +
             | 
| 245 | 
            +
                  def initialize(server_objects)
         | 
| 246 | 
            +
                    @servers = server_objects.map { |so| ServerObject.new(so) }
         | 
| 247 | 
            +
                    @servers.sort!
         | 
| 248 | 
            +
                  end
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                  def <=>(other)
         | 
| 251 | 
            +
                    @servers <=> other.servers
         | 
| 252 | 
            +
                  end
         | 
| 253 | 
            +
                end
         | 
| 190 254 | 
             
              end
         | 
| 191 255 | 
             
            end
         | 
| @@ -76,6 +76,47 @@ module OpenAPISourceTools | |
| 76 76 | 
             
                  end
         | 
| 77 77 | 
             
                  uniqs.keys.sort!.map { |k| uniqs[k] }
         | 
| 78 78 | 
             
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def response_codes(responses_object)
         | 
| 81 | 
            +
                  responses_object.keys.sort! do |a, b|
         | 
| 82 | 
            +
                    ad = a.downcase
         | 
| 83 | 
            +
                    bd = b.downcase
         | 
| 84 | 
            +
                    if ad == 'default'
         | 
| 85 | 
            +
                      1
         | 
| 86 | 
            +
                    elsif bd == 'default'
         | 
| 87 | 
            +
                      -1
         | 
| 88 | 
            +
                    else
         | 
| 89 | 
            +
                      ax = ad.end_with?('x')
         | 
| 90 | 
            +
                      bx = bd.end_with?('x')
         | 
| 91 | 
            +
                      if ax && bx || !ax && !bx
         | 
| 92 | 
            +
                        a <=> b # Both numbers or patterns.
         | 
| 93 | 
            +
                      else
         | 
| 94 | 
            +
                        ax ? 1 : -1
         | 
| 95 | 
            +
                      end
         | 
| 96 | 
            +
                    end
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                def response_code_condition(code, var: 'code', op_and: '&&', op_lte: '<=', op_eq: '==')
         | 
| 101 | 
            +
                  low = []
         | 
| 102 | 
            +
                  high = []
         | 
| 103 | 
            +
                  code.downcase.each_char do |c|
         | 
| 104 | 
            +
                    if c == 'x'
         | 
| 105 | 
            +
                      low.push('0')
         | 
| 106 | 
            +
                      high.push('9')
         | 
| 107 | 
            +
                    else
         | 
| 108 | 
            +
                      low.push(c)
         | 
| 109 | 
            +
                      high.push(c)
         | 
| 110 | 
            +
                    end
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
                  low = low.join
         | 
| 113 | 
            +
                  high = high.join
         | 
| 114 | 
            +
                  if low == high
         | 
| 115 | 
            +
                    "#{var} #{op_eq} #{low}"
         | 
| 116 | 
            +
                  else
         | 
| 117 | 
            +
                    "(#{low} #{op_lte} #{var}) #{op_and} (#{var} #{op_lte} #{high})"
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
                end
         | 
| 79 120 | 
             
              end
         | 
| 80 121 |  | 
| 81 122 | 
             
              # Task class to add an Helper instance to Gen.h, for convenience.
         | 
| @@ -46,13 +46,15 @@ module OpenAPISourceTools | |
| 46 46 | 
             
                  indent = 0
         | 
| 47 47 | 
             
                  blocks.each do |block|
         | 
| 48 48 | 
             
                    if block.nil?
         | 
| 49 | 
            -
                       | 
| 49 | 
            +
                      next
         | 
| 50 50 | 
             
                    elsif block.is_a?(Integer)
         | 
| 51 51 | 
             
                      indent += block
         | 
| 52 | 
            +
                      indent = 0 if indent.negative?
         | 
| 52 53 | 
             
                    elsif block.is_a?(TrueClass)
         | 
| 53 54 | 
             
                      indent += @config.indent_step
         | 
| 54 55 | 
             
                    elsif block.is_a?(FalseClass)
         | 
| 55 56 | 
             
                      indent -= @config.indent_step
         | 
| 57 | 
            +
                      indent = 0 if indent.negative?
         | 
| 56 58 | 
             
                    else
         | 
| 57 59 | 
             
                      block = block.to_s unless block.is_a?(String)
         | 
| 58 60 | 
             
                      if block.empty?
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: openapi-sourcetools
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.9.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Ismo Kärkkäinen
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2025- | 
| 11 | 
            +
            date: 2025-04-15 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: deep_merge
         | 
| @@ -47,6 +47,7 @@ executables: | |
| 47 47 | 
             
            - openapi-generate
         | 
| 48 48 | 
             
            - openapi-merge
         | 
| 49 49 | 
             
            - openapi-modifypaths
         | 
| 50 | 
            +
            - openapi-patterntests
         | 
| 50 51 | 
             
            - openapi-processpaths
         | 
| 51 52 | 
             
            extensions: []
         | 
| 52 53 | 
             
            extra_rdoc_files: []
         | 
| @@ -62,6 +63,7 @@ files: | |
| 62 63 | 
             
            - bin/openapi-generate
         | 
| 63 64 | 
             
            - bin/openapi-merge
         | 
| 64 65 | 
             
            - bin/openapi-modifypaths
         | 
| 66 | 
            +
            - bin/openapi-patterntests
         | 
| 65 67 | 
             
            - bin/openapi-processpaths
         | 
| 66 68 | 
             
            - lib/openapi/sourcetools.rb
         | 
| 67 69 | 
             
            - lib/openapi/sourcetools/apiobjects.rb
         |