meta-api 0.0.2 → 0.0.3
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/.gitignore +4 -4
- data/CHANGELOG.md +6 -1
- data/Gemfile.lock +1 -1
- data/README.md +9 -7
- data/docs/Rails.md +61 -0
- data/docs//345/246/202/344/275/225/350/264/241/347/214/256.md +10 -0
- data/docs//346/225/231/347/250/213.md +21 -21
- data/examples/rails_app/.gitattributes +5 -0
- data/examples/rails_app/.gitignore +23 -0
- data/examples/rails_app/.rspec +1 -0
- data/examples/rails_app/.ruby-version +1 -0
- data/examples/rails_app/Gemfile +29 -0
- data/examples/rails_app/Gemfile.lock +190 -0
- data/examples/rails_app/README.md +11 -0
- data/examples/rails_app/Rakefile +6 -0
- data/examples/rails_app/app/controllers/application_controller.rb +7 -0
- data/examples/rails_app/app/controllers/concerns/.keep +0 -0
- data/examples/rails_app/app/controllers/data_controller.rb +63 -0
- data/examples/rails_app/app/controllers/swagger_controller.rb +13 -0
- data/examples/rails_app/app/models/concerns/.keep +0 -0
- data/examples/rails_app/bin/rails +4 -0
- data/examples/rails_app/bin/rake +4 -0
- data/examples/rails_app/bin/setup +25 -0
- data/examples/rails_app/config/application.rb +39 -0
- data/examples/rails_app/config/boot.rb +3 -0
- data/examples/rails_app/config/credentials.yml.enc +1 -0
- data/examples/rails_app/config/environment.rb +5 -0
- data/examples/rails_app/config/environments/development.rb +51 -0
- data/examples/rails_app/config/environments/production.rb +65 -0
- data/examples/rails_app/config/environments/test.rb +50 -0
- data/examples/rails_app/config/initializers/cors.rb +16 -0
- data/examples/rails_app/config/initializers/filter_parameter_logging.rb +8 -0
- data/examples/rails_app/config/initializers/inflections.rb +16 -0
- data/examples/rails_app/config/initializers/meta_rails_plugin.rb +3 -0
- data/examples/rails_app/config/locales/en.yml +33 -0
- data/examples/rails_app/config/puma.rb +43 -0
- data/examples/rails_app/config/routes.rb +13 -0
- data/examples/rails_app/config.ru +6 -0
- data/examples/rails_app/lib/tasks/.keep +0 -0
- data/examples/rails_app/log/.keep +0 -0
- data/examples/rails_app/public/robots.txt +1 -0
- data/examples/rails_app/spec/data_controller_spec.rb +60 -0
- data/examples/rails_app/spec/rails_helper.rb +55 -0
- data/examples/rails_app/spec/spec_helper.rb +94 -0
- data/examples/rails_app/spec/swagger_controller_spec.rb +13 -0
- data/examples/rails_app/tmp/.keep +0 -0
- data/examples/rails_app/tmp/pids/.keep +0 -0
- data/lib/meta/application/execution.rb +3 -11
- data/lib/meta/application/{meta.rb → metadata.rb} +5 -15
- data/lib/meta/application/parameters.rb +47 -0
- data/lib/meta/application/route.rb +2 -2
- data/lib/meta/json_schema/builders/array_schema_builder.rb +11 -10
- data/lib/meta/json_schema/builders/dynamic_schema_builder.rb +23 -0
- data/lib/meta/json_schema/builders/object_schema_builder.rb +4 -18
- data/lib/meta/json_schema/builders/ref_schema_builder.rb +19 -0
- data/lib/meta/json_schema/builders/schema_builder_tool.rb +26 -1
- data/lib/meta/json_schema/schemas/array_schema.rb +3 -3
- data/lib/meta/json_schema/schemas/base_schema.rb +29 -69
- data/lib/meta/json_schema/schemas/dynamic_schema.rb +29 -0
- data/lib/meta/json_schema/schemas/object_schema.rb +27 -113
- data/lib/meta/json_schema/schemas/properties.rb +157 -0
- data/lib/meta/json_schema/schemas/ref_schema.rb +35 -0
- data/lib/meta/json_schema/schemas.rb +0 -2
- data/lib/meta/json_schema/support/schema_options.rb +21 -12
- data/lib/meta/json_schema.rb +2 -0
- data/lib/meta/rails.rb +98 -0
- data/lib/meta/route_dsl/parameters_builder.rb +2 -1
- data/lib/meta/swagger_doc.rb +5 -2
- data/lib/meta/utils/kwargs/builder.rb +115 -0
- data/lib/meta/utils/{kwargs.rb → kwargs/check.rb} +22 -22
- data/meta-api.gemspec +1 -1
- metadata +55 -4
| @@ -1,18 +1,28 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            -
            require_relative '../../utils/kwargs'
         | 
| 3 | 
            +
            require_relative '../../utils/kwargs/check'
         | 
| 4 4 |  | 
| 5 5 | 
             
            module Meta
         | 
| 6 6 | 
             
              module JsonSchema
         | 
| 7 7 | 
             
                class ObjectSchema < BaseSchema
         | 
| 8 | 
            -
                  attr_reader :properties, : | 
| 8 | 
            +
                  attr_reader :properties, :locked_options
         | 
| 9 9 |  | 
| 10 | 
            -
                   | 
| 10 | 
            +
                  USER_OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
         | 
| 11 | 
            +
                    permit_extras true
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    key :scope, normalizer: ->(value) {
         | 
| 14 | 
            +
                      raise ArgumentError, 'scope 选项不可传递 nil' if value.nil?
         | 
| 15 | 
            +
                      value = [value] unless value.is_a?(Array)
         | 
| 16 | 
            +
                      value
         | 
| 17 | 
            +
                    }
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def initialize(properties: nil, options: {}, locked_options: {}, schema_name_resolver: proc { nil })
         | 
| 11 21 | 
             
                    super(options)
         | 
| 12 22 |  | 
| 13 | 
            -
                    @properties = properties || {}
         | 
| 14 | 
            -
                    @ | 
| 15 | 
            -
                    @locked_options = locked_options || {}
         | 
| 23 | 
            +
                    @properties = properties || Properties.new({}) # property 包含 stage,stage 包含 scope、schema
         | 
| 24 | 
            +
                    @properties = Properties.new(@properties) if @properties.is_a?(Hash)
         | 
| 25 | 
            +
                    @locked_options = USER_OPTIONS_CHECKER.check(locked_options || {})
         | 
| 16 26 | 
             
                    @schema_name_resolver = schema_name_resolver || proc { nil }
         | 
| 17 27 | 
             
                  end
         | 
| 18 28 |  | 
| @@ -20,7 +30,6 @@ module Meta | |
| 20 30 | 
             
                  def dup(options)
         | 
| 21 31 | 
             
                    self.class.new(
         | 
| 22 32 | 
             
                      properties: properties,
         | 
| 23 | 
            -
                      object_validations: object_validations,
         | 
| 24 33 | 
             
                      options: options,
         | 
| 25 34 | 
             
                      locked_options: locked_options,
         | 
| 26 35 | 
             
                      schema_name_resolver: @schema_name_resolver
         | 
| @@ -30,52 +39,12 @@ module Meta | |
| 30 39 | 
             
                  def filter(object_value, user_options = {})
         | 
| 31 40 | 
             
                    # 合并 user_options
         | 
| 32 41 | 
             
                    user_options = user_options.merge(locked_options) if locked_options
         | 
| 42 | 
            +
                    user_options = USER_OPTIONS_CHECKER.check(user_options)
         | 
| 33 43 |  | 
| 34 44 | 
             
                    object_value = super(object_value, user_options)
         | 
| 35 | 
            -
                    return nil if object_value.nil?
         | 
| 36 45 |  | 
| 37 | 
            -
                     | 
| 38 | 
            -
                     | 
| 39 | 
            -
                    user_scope = user_options[:scope] || []
         | 
| 40 | 
            -
                    user_scope = [user_scope] unless user_scope.is_a?(Array)
         | 
| 41 | 
            -
                    stage = user_options[:stage]
         | 
| 42 | 
            -
                    exclude = user_options.delete(:exclude) # 这里删除 exclude 选项
         | 
| 43 | 
            -
                    filtered_properties = @properties.filter do |name, property_schema|
         | 
| 44 | 
            -
                      # 通过 discard_missing 过滤
         | 
| 45 | 
            -
                      next false if user_options[:discard_missing] && !object_value.key?(name.to_s)
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                      # 通过 stage 过滤。
         | 
| 48 | 
            -
                      property_schema_options = property_schema.options(stage)
         | 
| 49 | 
            -
                      next false unless property_schema_options
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                      # 通过 locked_exclude 选项过滤
         | 
| 52 | 
            -
                      next false if exclude && exclude.include?(name)
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                      # 通过 scope 过滤
         | 
| 55 | 
            -
                      scope_option = property_schema_options[:scope]
         | 
| 56 | 
            -
                      next true if scope_option.empty?
         | 
| 57 | 
            -
                      next false if user_scope.empty?
         | 
| 58 | 
            -
                      (user_scope - scope_option).empty? # user_scope 应被消耗殆尽
         | 
| 59 | 
            -
                    end
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                    # 第二步,递归过滤每一个属性
         | 
| 62 | 
            -
                    object = {}
         | 
| 63 | 
            -
                    errors = {}
         | 
| 64 | 
            -
                    filtered_properties.each do |name, property_schema|
         | 
| 65 | 
            -
                      value = resolve_property_value(object_value, name, property_schema, stage)
         | 
| 66 | 
            -
             | 
| 67 | 
            -
                      begin
         | 
| 68 | 
            -
                        object[name] = property_schema.filter(value, **user_options, object_value: object_value)
         | 
| 69 | 
            -
                      rescue JsonSchema::ValidationErrors => e
         | 
| 70 | 
            -
                        errors.merge! e.prepend_root(name).errors
         | 
| 71 | 
            -
                      end
         | 
| 72 | 
            -
                    end.to_h
         | 
| 73 | 
            -
             | 
| 74 | 
            -
                    if errors.empty?
         | 
| 75 | 
            -
                      object
         | 
| 76 | 
            -
                    else
         | 
| 77 | 
            -
                      raise JsonSchema::ValidationErrors.new(errors)
         | 
| 78 | 
            -
                    end
         | 
| 46 | 
            +
                    return nil if object_value.nil?
         | 
| 47 | 
            +
                    @properties.filter(object_value, user_options)
         | 
| 79 48 | 
             
                  end
         | 
| 80 49 |  | 
| 81 50 | 
             
                  # 合并其他的属性,并返回一个新的 ObjectSchema
         | 
| @@ -83,61 +52,18 @@ module Meta | |
| 83 52 | 
             
                    ObjectSchema.new(properties: self.properties.merge(properties))
         | 
| 84 53 | 
             
                  end
         | 
| 85 54 |  | 
| 86 | 
            -
                   | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
                   | 
| 90 | 
            -
                  # - schemas: 用于保存已经生成的 Schema
         | 
| 91 | 
            -
                  # - to_ref: 是否生成 $ref 格式,默认为“是”
         | 
| 92 | 
            -
                  #
         | 
| 93 | 
            -
                  # 提示:
         | 
| 94 | 
            -
                  # > 每个 ObjectSchema 拥有一个 @schema_name_resolver 实例变量,如果由它解析出来的名称不为 nil,
         | 
| 95 | 
            -
                  # > 则该 Schema 生成文档时会使用 $ref 格式。除非 to_ref 选项设置为 false.
         | 
| 96 | 
            -
                  #
         | 
| 97 | 
            -
                  def to_schema_doc(user_options)
         | 
| 98 | 
            -
                    Utils::KeywordArgs.check(
         | 
| 99 | 
            -
                      args: user_options,
         | 
| 100 | 
            -
                      schema: { stage: nil, scope: nil, to_ref: false, schemas: nil }
         | 
| 101 | 
            -
                    )
         | 
| 55 | 
            +
                  def resolve_name(stage)
         | 
| 56 | 
            +
                    locked_scopes = (locked_options || {})[:scope] || []
         | 
| 57 | 
            +
                    @schema_name_resolver.call(stage, locked_scopes)
         | 
| 58 | 
            +
                  end
         | 
| 102 59 |  | 
| 103 | 
            -
             | 
| 60 | 
            +
                  def to_schema_doc(stage: nil, **user_options)
         | 
| 104 61 | 
             
                    locked_scopes = (locked_options || {})[:scope] || []
         | 
| 105 | 
            -
                    locked_scopes = [locked_scopes] unless locked_scope.nil? && locked_scopes.is_a?(Array)
         | 
| 106 | 
            -
                    schema_name = @schema_name_resolver.call(stage, locked_scopes)
         | 
| 107 | 
            -
                    if schema_name && user_options[:to_ref] != false
         | 
| 108 | 
            -
                      # 首先将 Schema 写进 schemas 选项中去
         | 
| 109 | 
            -
                      schemas = user_options[:schemas]
         | 
| 110 | 
            -
                      unless schemas.key?(schema_name)
         | 
| 111 | 
            -
                        schemas[schema_name] = nil # 首先设置 schemas 防止出现无限循环
         | 
| 112 | 
            -
                        schemas[schema_name] = to_schema_doc(**user_options, to_ref: false) # 原地修改 schemas,无妨
         | 
| 113 | 
            -
                      end
         | 
| 114 | 
            -
             | 
| 115 | 
            -
                      return { '$ref': "#/components/schemas/#{schema_name}" }
         | 
| 116 | 
            -
                    end
         | 
| 117 | 
            -
             | 
| 118 | 
            -
                    stage_options = options(stage)
         | 
| 119 | 
            -
                    properties = @properties.filter do |name, property_schema|
         | 
| 120 | 
            -
                      # 根据 stage 过滤
         | 
| 121 | 
            -
                      next false if stage.nil?
         | 
| 122 | 
            -
                      next false if stage == :param && !property_schema.options(:param)
         | 
| 123 | 
            -
                      next false if stage == :render && !property_schema.options(:render)
         | 
| 124 | 
            -
             | 
| 125 | 
            -
                      # 根据 locked_scope 过滤
         | 
| 126 | 
            -
                      next true if locked_scopes.empty? # locked_scope 未提供时不过滤
         | 
| 127 | 
            -
                      property_scope = property_schema.options(stage, :scope)
         | 
| 128 | 
            -
                      property_scope = [property_scope] unless property_scope.is_a?(Array)
         | 
| 129 | 
            -
                      next true if property_scope.empty?
         | 
| 130 | 
            -
                      (locked_scopes - property_scope).empty? # user_scope 应被消耗殆尽
         | 
| 131 | 
            -
                    end
         | 
| 132 | 
            -
                    required_keys = properties.filter do |key, property_schema|
         | 
| 133 | 
            -
                      property_schema.options(stage, :required)
         | 
| 134 | 
            -
                    end.keys
         | 
| 135 | 
            -
                    properties = properties.transform_values do |property_schema|
         | 
| 136 | 
            -
                      property_schema.to_schema_doc(**user_options, to_ref: true)
         | 
| 137 | 
            -
                    end
         | 
| 138 62 |  | 
| 139 63 | 
             
                    schema = { type: 'object' }
         | 
| 140 | 
            -
                    schema[:description] =  | 
| 64 | 
            +
                    schema[:description] = options[:description] if options[:description]
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                    properties, required_keys = @properties.to_swagger_doc(stage: stage, locked_scopes: locked_scopes, **user_options)
         | 
| 141 67 | 
             
                    schema[:properties] = properties unless properties.empty?
         | 
| 142 68 | 
             
                    schema[:required] = required_keys unless required_keys.empty?
         | 
| 143 69 | 
             
                    schema
         | 
| @@ -150,18 +76,6 @@ module Meta | |
| 150 76 | 
             
                  def locked_exclude
         | 
| 151 77 | 
             
                    locked_options && locked_options[:exclude]
         | 
| 152 78 | 
             
                  end
         | 
| 153 | 
            -
             | 
| 154 | 
            -
                  private
         | 
| 155 | 
            -
             | 
| 156 | 
            -
                    def resolve_property_value(object_value, name, property_schema, stage)
         | 
| 157 | 
            -
                      if property_schema.value?(stage)
         | 
| 158 | 
            -
                        nil
         | 
| 159 | 
            -
                      elsif object_value.is_a?(Hash) || object_value.is_a?(ObjectWrapper)
         | 
| 160 | 
            -
                        object_value.key?(name.to_s) ? object_value[name.to_s] : object_value[name.to_sym]
         | 
| 161 | 
            -
                      else
         | 
| 162 | 
            -
                        raise "不应该还有其他类型了,已经在类型转换中将其转换为 Meta::JsonSchema::ObjectWrapper 了"
         | 
| 163 | 
            -
                      end
         | 
| 164 | 
            -
                    end
         | 
| 165 79 | 
             
                end
         | 
| 166 80 | 
             
              end
         | 
| 167 81 | 
             
            end
         | 
| @@ -0,0 +1,157 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'forwardable'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Meta
         | 
| 6 | 
            +
              module JsonSchema
         | 
| 7 | 
            +
                class Properties
         | 
| 8 | 
            +
                  class StagingProperty
         | 
| 9 | 
            +
                    def initialize(param:, render:, none:)
         | 
| 10 | 
            +
                      @param_stage = param
         | 
| 11 | 
            +
                      @render_stage = render
         | 
| 12 | 
            +
                      @none_stage = none
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    def stage(stage = nil)
         | 
| 16 | 
            +
                      case stage
         | 
| 17 | 
            +
                      when :param
         | 
| 18 | 
            +
                        @param_stage
         | 
| 19 | 
            +
                      when :render
         | 
| 20 | 
            +
                        @render_stage
         | 
| 21 | 
            +
                      else
         | 
| 22 | 
            +
                        @none_stage
         | 
| 23 | 
            +
                      end
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    def stage?(stage)
         | 
| 27 | 
            +
                      stage(stage) != nil
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    def schema(stage = nil)
         | 
| 31 | 
            +
                      stage(stage).schema
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    def self.build(options, build_schema)
         | 
| 35 | 
            +
                      param_opts, render_opts, common_opts = SchemaOptions.divide_to_param_and_render(options)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      StagingProperty.new(
         | 
| 38 | 
            +
                        param: options[:param] === false ? nil : ScopingProperty.build(param_opts, build_schema),
         | 
| 39 | 
            +
                        render: options[:render] === false ? nil : ScopingProperty.build(render_opts, build_schema),
         | 
| 40 | 
            +
                        none: ScopingProperty.build(common_opts, build_schema)
         | 
| 41 | 
            +
                      )
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  class ScopingProperty
         | 
| 46 | 
            +
                    attr_reader :scope, :schema
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    def initialize(scope: :all, schema:)
         | 
| 49 | 
            +
                      scope = :all if scope.nil?
         | 
| 50 | 
            +
                      scope = [scope] unless scope.is_a?(Array) || scope == :all
         | 
| 51 | 
            +
                      if scope.is_a?(Array) && scope.any? { |s| s.is_a?(Integer) }
         | 
| 52 | 
            +
                        raise ArgumentError, 'scope 选项内不可传递数字'
         | 
| 53 | 
            +
                      end
         | 
| 54 | 
            +
                      @scope = scope
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                      @schema = schema
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    def self.build(options, build_schema)
         | 
| 60 | 
            +
                      options = options.dup
         | 
| 61 | 
            +
                      scope = options.delete(:scope)
         | 
| 62 | 
            +
                      schema = build_schema.call(options)
         | 
| 63 | 
            +
                      ScopingProperty.new(scope: scope, schema: schema)
         | 
| 64 | 
            +
                    end
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  extend Forwardable
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  def initialize(properties)
         | 
| 70 | 
            +
                    @properties = properties
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  def filter(object_value, user_options = {})
         | 
| 74 | 
            +
                    # 第一步,根据 user_options[:scope] 需要过滤一些字段
         | 
| 75 | 
            +
                    stage = user_options[:stage]
         | 
| 76 | 
            +
                    # 传递一个数字;因为 scope 不能包含数字,这里传递一个数字,使得凡是配置 scope 的属性都会被过滤
         | 
| 77 | 
            +
                    user_scope = user_options[:scope] || [0]
         | 
| 78 | 
            +
                    exclude = user_options.delete(:exclude) # 这里删除 exclude 选项,不要传递给下一层
         | 
| 79 | 
            +
                    properties = filter_by(stage: stage, user_scope: user_scope)
         | 
| 80 | 
            +
                    filtered_properties = properties.filter do |name, property|
         | 
| 81 | 
            +
                      # 通过 discard_missing 过滤
         | 
| 82 | 
            +
                      next false if user_options[:discard_missing] && !object_value.key?(name.to_s)
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                      # 通过 locked_exclude 选项过滤
         | 
| 85 | 
            +
                      next false if exclude && exclude.include?(name)
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                      # 默认返回 true
         | 
| 88 | 
            +
                      next true
         | 
| 89 | 
            +
                    end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    # 第二步,递归过滤每一个属性
         | 
| 92 | 
            +
                    object = {}
         | 
| 93 | 
            +
                    errors = {}
         | 
| 94 | 
            +
                    filtered_properties.each do |name, property_schema|
         | 
| 95 | 
            +
                      value = resolve_property_value(object_value, name, property_schema, stage)
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                      begin
         | 
| 98 | 
            +
                        object[name] = property_schema.filter(value, **user_options, object_value: object_value)
         | 
| 99 | 
            +
                      rescue JsonSchema::ValidationErrors => e
         | 
| 100 | 
            +
                        errors.merge! e.prepend_root(name).errors
         | 
| 101 | 
            +
                      end
         | 
| 102 | 
            +
                    end.to_h
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                    if errors.empty?
         | 
| 105 | 
            +
                      object
         | 
| 106 | 
            +
                    else
         | 
| 107 | 
            +
                      raise JsonSchema::ValidationErrors.new(errors)
         | 
| 108 | 
            +
                    end
         | 
| 109 | 
            +
                  end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  def to_swagger_doc(locked_scopes:, stage:, **user_options)
         | 
| 112 | 
            +
                    properties = filter_by(stage: stage, user_scope: locked_scopes)
         | 
| 113 | 
            +
                    required_keys = properties.filter do |key, property_schema|
         | 
| 114 | 
            +
                      property_schema.options[:required]
         | 
| 115 | 
            +
                    end.keys
         | 
| 116 | 
            +
                    properties = properties.transform_values do |property_schema |
         | 
| 117 | 
            +
                      property_schema.to_schema_doc(stage: stage, **user_options)
         | 
| 118 | 
            +
                    end
         | 
| 119 | 
            +
                    [properties, required_keys]
         | 
| 120 | 
            +
                  end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                  # 程序中有些地方用到了这三个方法
         | 
| 123 | 
            +
                  def_delegators :@properties, :empty?, :key?, :[]
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                  def self.build_property(*args)
         | 
| 126 | 
            +
                    StagingProperty.build(*args)
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                  private
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                  def filter_by(stage:, user_scope: false)
         | 
| 132 | 
            +
                    properties = @properties.filter do |name, property|
         | 
| 133 | 
            +
                      # 通过 stage 过滤。
         | 
| 134 | 
            +
                      next false unless property.stage?(stage)
         | 
| 135 | 
            +
                      property = property.stage(stage)
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                      # 通过 user_scope 过滤
         | 
| 138 | 
            +
                      next true if property.scope == :all
         | 
| 139 | 
            +
                      (user_scope - property.scope).empty? # user_scope 应被消耗殆尽
         | 
| 140 | 
            +
                    end
         | 
| 141 | 
            +
                    properties.transform_values do |property|
         | 
| 142 | 
            +
                      property.stage(stage).schema
         | 
| 143 | 
            +
                    end
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  def resolve_property_value(object_value, name, property_schema, stage)
         | 
| 147 | 
            +
                    if property_schema.value?
         | 
| 148 | 
            +
                      nil
         | 
| 149 | 
            +
                    elsif object_value.is_a?(Hash) || object_value.is_a?(ObjectWrapper)
         | 
| 150 | 
            +
                      object_value.key?(name.to_s) ? object_value[name.to_s] : object_value[name.to_sym]
         | 
| 151 | 
            +
                    else
         | 
| 152 | 
            +
                      raise "不应该还有其他类型了,已经在类型转换中将其转换为 Meta::JsonSchema::ObjectWrapper 了"
         | 
| 153 | 
            +
                    end
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
              end
         | 
| 157 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'base_schema'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Meta
         | 
| 6 | 
            +
              module JsonSchema
         | 
| 7 | 
            +
                class RefSchema < BaseSchema
         | 
| 8 | 
            +
                  attr_reader :schema
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize(schema, options = {})
         | 
| 11 | 
            +
                    super(options)
         | 
| 12 | 
            +
                    @schema = schema
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def filter(value, user_options = {})
         | 
| 16 | 
            +
                    value = super
         | 
| 17 | 
            +
                    schema.filter(value, user_options)
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def to_schema_doc(user_options)
         | 
| 21 | 
            +
                    schema_name = schema.resolve_name(user_options[:stage])
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    # 首先将 Schema 写进 schemas 选项中去
         | 
| 24 | 
            +
                    schema_components = user_options[:schemas]
         | 
| 25 | 
            +
                    unless schema_components.key?(schema_name)
         | 
| 26 | 
            +
                      schema_components[schema_name] = nil # 首先设置 schemas 防止出现无限循环
         | 
| 27 | 
            +
                      schema_components[schema_name] = schema.to_schema_doc(**user_options) # 原地修改 schemas,无妨
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    # 返回的是 $ref 结构
         | 
| 31 | 
            +
                    { '$ref': "#/components/schemas/#{schema_name}" }
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -7,6 +7,4 @@ require_relative 'support/presenters' | |
| 7 7 | 
             
            require_relative 'schemas/base_schema'
         | 
| 8 8 | 
             
            require_relative 'schemas/object_schema'
         | 
| 9 9 | 
             
            require_relative 'schemas/array_schema'
         | 
| 10 | 
            -
            require_relative 'builders/object_schema_builder'
         | 
| 11 | 
            -
            require_relative 'builders/array_schema_builder'
         | 
| 12 10 | 
             
            require_relative 'builders/schema_builder_tool'
         | 
| @@ -1,8 +1,17 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            require_relative '../../utils/kwargs/builder'
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            module Meta
         | 
| 4 6 | 
             
              module JsonSchema
         | 
| 5 7 | 
             
                module SchemaOptions
         | 
| 8 | 
            +
                  OPTIONS_CHECKER = Utils::KeywordArgs::Builder.build do
         | 
| 9 | 
            +
                    key :type, :items, :description, :presenter, :value, :format, :required, :default, :validate, :allowable, :properties, :convert
         | 
| 10 | 
            +
                    key :using, normalizer: ->(value) { value.is_a?(Proc) ? { resolve: value } : value }
         | 
| 11 | 
            +
                    key :param
         | 
| 12 | 
            +
                    key :render
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 6 15 | 
             
                  @default_options = {
         | 
| 7 16 | 
             
                    scope: [],
         | 
| 8 17 | 
             
                    required: false
         | 
| @@ -14,29 +23,21 @@ module Meta | |
| 14 23 | 
             
                  ).uniq
         | 
| 15 24 |  | 
| 16 25 | 
             
                  class << self
         | 
| 17 | 
            -
                    def  | 
| 26 | 
            +
                    def divide_to_param_and_render(options)
         | 
| 18 27 | 
             
                      common_opts = (options || {}).dup
         | 
| 19 28 | 
             
                      param_opts = common_opts.delete(:param)
         | 
| 20 29 | 
             
                      render_opts = common_opts.delete(:render)
         | 
| 21 30 |  | 
| 22 31 | 
             
                      param_opts = merge_common_to_stage(common_opts, param_opts)
         | 
| 23 32 | 
             
                      render_opts = merge_common_to_stage(common_opts, render_opts)
         | 
| 24 | 
            -
                      [param_opts, render_opts]
         | 
| 25 | 
            -
                    end
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                    def merge_common_to_stage(common_opts, stage_opts)
         | 
| 28 | 
            -
                      stage_opts = {} if stage_opts.nil? || stage_opts == true
         | 
| 29 | 
            -
                      stage_opts = common_opts.merge(stage_opts) if stage_opts
         | 
| 30 | 
            -
                      stage_opts = normalize(stage_opts) if stage_opts
         | 
| 31 | 
            -
                      stage_opts
         | 
| 33 | 
            +
                      [param_opts, render_opts, common_opts]
         | 
| 32 34 | 
             
                    end
         | 
| 33 35 |  | 
| 34 36 | 
             
                    def normalize(options)
         | 
| 37 | 
            +
                      options = OPTIONS_CHECKER.check(options)
         | 
| 38 | 
            +
             | 
| 35 39 | 
             
                      # 只要 options 中设置为 nil 的选项没有明确的意义,则下行代码是永远有效的
         | 
| 36 40 | 
             
                      options = (@default_options.compact).merge(options.compact)
         | 
| 37 | 
            -
                      options[:scope] = [options[:scope]] unless options[:scope].is_a?(Array)
         | 
| 38 | 
            -
                      # TODO: 更好的规范选项的方式,以及如何检查深层次嵌套下参数类型的错误
         | 
| 39 | 
            -
                      options[:using] = { resolve: options[:using] } if options[:using].is_a?(Proc)
         | 
| 40 41 | 
             
                      if options[:using]
         | 
| 41 42 | 
             
                        if options[:type].nil?
         | 
| 42 43 | 
             
                          options[:type] = 'object'
         | 
| @@ -51,6 +52,14 @@ module Meta | |
| 51 52 |  | 
| 52 53 | 
             
                      options
         | 
| 53 54 | 
             
                    end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                    private
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                    def merge_common_to_stage(common_opts, stage_opts)
         | 
| 59 | 
            +
                      stage_opts = {} if stage_opts.nil? || stage_opts == true
         | 
| 60 | 
            +
                      stage_opts = common_opts.merge(stage_opts) if stage_opts
         | 
| 61 | 
            +
                      stage_opts
         | 
| 62 | 
            +
                    end
         | 
| 54 63 | 
             
                  end
         | 
| 55 64 | 
             
                end
         | 
| 56 65 | 
             
              end
         | 
    
        data/lib/meta/rails.rb
    ADDED
    
    | @@ -0,0 +1,98 @@ | |
| 1 | 
            +
            # 作为 Rails 插件
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'errors'
         | 
| 4 | 
            +
            require_relative 'json_schema/schemas'
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module Meta
         | 
| 7 | 
            +
              module Rails
         | 
| 8 | 
            +
                def self.setup
         | 
| 9 | 
            +
                  # 第一步,为 ActionController 添加一个新的 Renderer
         | 
| 10 | 
            +
                  ActionController::Renderers.add :json_on_schema do |obj, options|
         | 
| 11 | 
            +
                    options = options.dup
         | 
| 12 | 
            +
                    status = options.delete(:status) || 200
         | 
| 13 | 
            +
                    scope = options.delete(:scope) || :all
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    route_definitions = self.class.route_definitions
         | 
| 16 | 
            +
                    route_definition = route_definitions[[self.class, params[:action].to_sym]]
         | 
| 17 | 
            +
                    raise '未绑定 Route 定义' unless route_definition
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    meta_definition = route_definition.meta
         | 
| 20 | 
            +
                    raise '未提供 status 宏定义' unless meta_definition[:responses] && meta_definition[:responses][status]
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    render_schema = meta_definition[:responses][status]
         | 
| 23 | 
            +
                    str = render_schema.filter(obj, execution: self, stage: :render, scope: scope)
         | 
| 24 | 
            +
                    render json: str, **options
         | 
| 25 | 
            +
                  rescue JsonSchema::ValidationErrors => e
         | 
| 26 | 
            +
                    raise Errors::RenderingInvalid.new(e.errors)
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                module Plugin
         | 
| 31 | 
            +
                  def self.generate_swagger_doc(klass, **kwargs)
         | 
| 32 | 
            +
                    paths_and_routes = klass.route_definitions.values.map do |route_definition|
         | 
| 33 | 
            +
                      [route_definition.path, route_definition]
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
                    SwaggerDocUtil.generate_from_paths_and_routes(paths_and_routes, **kwargs)
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def self.included(base)
         | 
| 39 | 
            +
                    # 已经被父类引入过,不再重复引入
         | 
| 40 | 
            +
                    return if self.respond_to?(:route_definitions)
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    # 为 ActionController 引入宏命令,宏命令在子类中生效
         | 
| 43 | 
            +
                    base.extend ClassMethods
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    # 为 ActionController 定义一个 Route Definitions,子类实例可以通过 self.class.route_definitions 访问
         | 
| 46 | 
            +
                    route_definitions = {}
         | 
| 47 | 
            +
                    base.define_singleton_method(:route_definitions) { route_definitions }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    # 定义一个方法,子类在定义方法后,将当前的路由定义应用到该方法上
         | 
| 50 | 
            +
                    base.define_singleton_method(:apply_route_definition) do |klass, method_name|
         | 
| 51 | 
            +
                      if @current_route_builder
         | 
| 52 | 
            +
                        self.route_definitions[[klass, method_name]] = @current_route_builder.build
         | 
| 53 | 
            +
                        @current_route_builder = nil
         | 
| 54 | 
            +
                      end
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
                    # 触发 apply_route_definition 方法
         | 
| 57 | 
            +
                    base.define_singleton_method(:method_added) do |name|
         | 
| 58 | 
            +
                      apply_route_definition(self, name)
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    # 为 ActionController 定义一个方法,用于获取过滤后的参数
         | 
| 62 | 
            +
                    attr_accessor :params_on_schema
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                    # 为 ActionController 定义一个 before_action,用于过滤参数
         | 
| 65 | 
            +
                    base.before_action do
         | 
| 66 | 
            +
                      route_definitions = self.class.route_definitions
         | 
| 67 | 
            +
                      route_definition = route_definitions[[self.class, params[:action].to_sym]]
         | 
| 68 | 
            +
                      next unless route_definition
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                      meta_definition = route_definition.meta
         | 
| 71 | 
            +
                      next if meta_definition[:parameters].empty? && meta_definition[:request_body].nil?
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                      raw_params = self.params.to_unsafe_h
         | 
| 74 | 
            +
                      final_params = {}
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                      if meta_definition[:parameters]
         | 
| 77 | 
            +
                        parameters_meta = meta_definition[:parameters]
         | 
| 78 | 
            +
                        final_params = parameters_meta.filter(request)
         | 
| 79 | 
            +
                      end
         | 
| 80 | 
            +
                      if meta_definition[:request_body]
         | 
| 81 | 
            +
                        params_schema = meta_definition[:request_body]
         | 
| 82 | 
            +
                        final_params.merge! params_schema.filter(raw_params, stage: :param)
         | 
| 83 | 
            +
                      end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                      self.params_on_schema = final_params
         | 
| 86 | 
            +
                    rescue JsonSchema::ValidationErrors => e
         | 
| 87 | 
            +
                      raise Errors::ParameterInvalid.new(e.errors)
         | 
| 88 | 
            +
                    end
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  module ClassMethods
         | 
| 92 | 
            +
                    def route(path = '', method = :all, &block)
         | 
| 93 | 
            +
                      @current_route_builder = RouteDSL::RouteBuilder.new(path, method, &block)
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
            end
         | 
    
        data/lib/meta/swagger_doc.rb
    CHANGED
    
    | @@ -4,10 +4,13 @@ module Meta | |
| 4 4 | 
             
              module SwaggerDocUtil
         | 
| 5 5 | 
             
                class << self
         | 
| 6 6 | 
             
                  def generate(application, info: {}, servers: [])
         | 
| 7 | 
            -
                     | 
| 7 | 
            +
                    paths_and_routes = get_paths_and_routes!(application)
         | 
| 8 | 
            +
                    return generate_from_paths_and_routes(paths_and_routes, info: info, servers: servers)
         | 
| 9 | 
            +
                  end
         | 
| 8 10 |  | 
| 11 | 
            +
                  def generate_from_paths_and_routes(paths_and_routes, info: {}, servers: [])
         | 
| 9 12 | 
             
                    schemas = {}
         | 
| 10 | 
            -
                    paths =  | 
| 13 | 
            +
                    paths = paths_and_routes.group_by { |path, route| path }.map { |path, routes| [path, routes.map { |item| item[1] }]}.map do |path, routes|
         | 
| 11 14 | 
             
                      operations = routes.map do |route|
         | 
| 12 15 | 
             
                        [route.method.downcase.to_sym, generate_operation_object(route, schemas)]
         | 
| 13 16 | 
             
                      end.to_h
         |