meta-api 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09b85e846db5773168de2e283df9b42ddb22cf410f3c4696cfbb65c091c6b24f'
4
- data.tar.gz: 95ad752071f7601069da746240750301e286ea880749bb5a071e4f1192d838f3
3
+ metadata.gz: 7364c618306dc9d04e6e08784670157e64b57f3f90b1828a259ba713268bb281
4
+ data.tar.gz: 00d25192ac7e4b4c733f1aa0d44a2424290d2bc4e290ca99d810686b744ec102
5
5
  SHA512:
6
- metadata.gz: 7053ba6b9eac44a5b59bd0429a615c00e2d561708fad5d68abd00c7bf7f4680dc3ef710f593dd9001a7c222f86e340f99886892eff0fc672938b223456795e85
7
- data.tar.gz: e270051a232829e6c9fef9f21b8d181ba466b981860e0d34599cba50b633b324181b03982d040c2470066cc4491be01483ddb093b81d69e7c6ed2c9a69caaf6e
6
+ metadata.gz: 62eabc56bc4ef76f76cb131f5b901a32875e12b8905857529307dee1acd0a4a354e2822f622f9ef43939444e89e08933e025404050a4347a77fbf169f8a7fe9f
7
+ data.tar.gz: 6692c9733a12d3acccc18d1cf6227a084c926dfd69b4acaae872e553683b4e70ca85d1aca874d87c169be1c2a68ca51e62ede1c142ef070829f508b7c2820426
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  .DS_Store
2
+ *.gem
2
3
  /.idea
3
4
  /.ruby-version
4
5
 
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # 更新日志
2
+
3
+ ## V0.0.2(2023 年 3 月 8 日)
4
+
5
+ 1. 添加两个配置项,适合生产环境下使用,用以关闭渲染时的数据验证验证。
6
+ 2. 添加对多态实体的支持。
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- meta-api (0.0.1)
4
+ meta-api (0.0.2)
5
5
 
6
6
  GEM
7
7
  remote: https://gems.ruby-china.com/
data/README.md CHANGED
@@ -9,7 +9,7 @@ Meta 框架是一个适用于 Web API 的后端框架,采用 Ruby 语言编写
9
9
  你可直接使用我的脚手架项目上手体验:
10
10
 
11
11
  ```bash
12
- $ git clone https://github.com/yetrun/web-frame-example
12
+ $ git clone https://github.com/yetrun/web-frame-example.git
13
13
  ```
14
14
 
15
15
  ## 安装
@@ -17,7 +17,7 @@ $ git clone https://github.com/yetrun/web-frame-example
17
17
  在 Gemfile 中添加:
18
18
 
19
19
  ```ruby
20
- gem 'meta-api', git: 'https://github.com/yetrun/web-frame'
20
+ gem 'meta-api', '~> 0.0.1' # Meta 框架处于快速开发阶段,引入时应尽量固定版本
21
21
  ```
22
22
 
23
23
  然后在 Ruby 代码中引用:
@@ -144,6 +144,10 @@ run NotesAPI
144
144
  - [教程](docs/教程.md)
145
145
  - [索引](docs/索引.md)
146
146
 
147
+ ## 支持
148
+
149
+ 加 QQ 群(489579810)可获得实时答疑。
150
+
147
151
  ## License
148
152
 
149
153
  LGPL-2.1
@@ -1032,6 +1032,27 @@ action do
1032
1032
  end
1033
1033
  ```
1034
1034
 
1035
+ ### 多态参数和返回值
1036
+
1037
+ 定义属性时可定义多态类型,`using` 选项可接受一个块,它根据值来返回指定的类型:
1038
+
1039
+ ```ruby
1040
+ property :target, using: ->(value) {
1041
+ # 根据 value.target_type 值返回实体类型
1042
+ # 例如,value.target_type == 'UserEntity',将返回 UserEntity 类
1043
+ value.target_type.constantize
1044
+ }
1045
+ ```
1046
+
1047
+ 或者接受一个 Hash,这时可提供 `one_of` 选项为文档生成提供加成:
1048
+
1049
+ ```ruby
1050
+ property :animal, using: {
1051
+ one_of: [CatEntity, DogEntity, PigEntity],
1052
+ resolve: ->(value) { value.animal_type.constantize }
1053
+ }
1054
+ ```
1055
+
1035
1056
  ## 生成文档
1036
1057
 
1037
1058
  应用模块提供一个 `to_swagger_doc` 方法生成 Open API 规格文档,该文档可被 Swagger UI 或基于 Swagger UI 的引擎渲染。
@@ -1058,9 +1079,18 @@ DemoApp.to_swagger_doc(
1058
1079
  >
1059
1080
  > 了解 [Swagger UI](https://swagger.io/tools/swagger-ui/).
1060
1081
 
1061
- ## 特殊用法举例
1082
+ ## 全局配置
1083
+
1084
+ ### 关闭渲染时验证
1085
+
1086
+ 默认情况下渲染时也会执行验证,以保证渲染的数据有效性,尽快发现错误。这在开发环境有用,但在生产环境没有必要。可使用下面的代码关闭渲染时的验证:
1062
1087
 
1063
- ##
1088
+ ```ruby
1089
+ Meta.config.render_type_conversion = false # 渲染时不执行类型转换
1090
+ Meta.config.render_validation = false # 渲染时不执行数据验证
1091
+ ```
1092
+
1093
+ ## 特殊用法举例
1064
1094
 
1065
1095
  ### 路由中实体定义的特殊用法
1066
1096
 
data/lib/meta/api.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require_relative 'config'
1
2
  require_relative 'application'
2
3
  require_relative 'swagger_doc'
3
4
  require_relative 'load_i18n'
@@ -83,7 +83,7 @@ module Meta
83
83
  end.to_h.freeze
84
84
  end
85
85
 
86
- # REVIEW: parse_params 不再解析参数了,而只是设置 @params_schema,并清理父路由解析的变量
86
+ # parse_params 不再解析参数了,而只是设置 @params_schema,并清理父路由解析的变量
87
87
  def parse_params(params_schema)
88
88
  @params_schema = params_schema
89
89
  end
@@ -107,7 +107,7 @@ module Meta
107
107
  options = {}
108
108
  end
109
109
 
110
- new_hash = entity_schema.filter(hash, **options, execution: self, stage: :render)
110
+ new_hash = entity_schema.filter(hash, **options, execution: self, stage: :render, validation: ::Meta.config.render_validation, type_conversion: ::Meta.config.render_type_conversion)
111
111
  response.body = [JSON.generate(new_hash)]
112
112
  else
113
113
  # 渲染多键值结点
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Meta
4
+ class Config
5
+ attr_accessor :render_validation, :render_type_conversion
6
+
7
+ def initialize
8
+ @render_type_conversion = true
9
+ @render_validation = true
10
+ end
11
+ end
12
+
13
+ @config = Config.new
14
+ class << self
15
+ attr_reader :config
16
+ end
17
+ end
data/lib/meta/entity.rb CHANGED
@@ -4,7 +4,6 @@ require 'forwardable'
4
4
  require_relative 'errors'
5
5
  require_relative 'json_schema/schemas'
6
6
 
7
- # Meta::Entity 是 ObjectSchemaBuilder 的一个类封装,它不应有自己的逻辑
8
7
  module Meta
9
8
  class Entity
10
9
  class << self
@@ -42,21 +42,21 @@ module Meta
42
42
 
43
43
  def property(name, options = {}, &block)
44
44
  name = name.to_sym
45
+ # REVIEW: 为何要 dup,删掉试试
45
46
  options = options.dup
46
47
 
47
- # 能且仅能 ObjectSchemaBuilder 内能使用 using 选项
48
- block = options[:using] unless block_given?
49
- if block.nil? || block.is_a?(Proc)
50
- @properties[name] = SchemaBuilderTool.build(options, &block)
51
- elsif block.respond_to?(:to_schema)
52
- schema = block.to_schema
48
+ using = options[:using]
49
+ if using.respond_to?(:to_schema)
50
+ schema = using.to_schema
53
51
  if options[:type] == 'array'
54
52
  @properties[name] = ArraySchema.new(schema, options)
55
53
  else
56
54
  @properties[name] = schema.dup(options)
57
55
  end
56
+ elsif using.is_a?(Proc) || using.is_a?(Hash) || using.nil?
57
+ @properties[name] = SchemaBuilderTool.build(options, &block)
58
58
  else
59
- raise "非法的参数。应传递代码块,或通过 using 选项传递 Proc、ObjectScope 或接受 `to_schema` 方法的对象。当前传递:#{block}"
59
+ raise "非法的 `using` 选项,应传递具有 `to_schema` 方法(如 Entity、Schema 等)或 Hash、Proc(动态生成 Schema)。当前传递:#{block}"
60
60
  end
61
61
  end
62
62
 
@@ -43,6 +43,22 @@ module Meta
43
43
  end
44
44
 
45
45
  def filter(value, user_options = {})
46
+ user_options = Utils::KeywordArgs.check(
47
+ args: user_options,
48
+ schema: {
49
+ stage: nil,
50
+ execution: nil,
51
+ object_value: nil,
52
+ type_conversion: true,
53
+ validation: true,
54
+
55
+ # 以下三个是 ObjectSchema 需要的选项
56
+ discard_missing: false,
57
+ exclude: [],
58
+ scope: []
59
+ }
60
+ )
61
+
46
62
  stage_options = options(user_options[:stage])
47
63
 
48
64
  value = resolve_value(user_options) if stage_options[:value]
@@ -50,9 +66,10 @@ module Meta
50
66
  value = stage_options[:default] if value.nil? && stage_options.key?(:default)
51
67
  value = stage_options[:convert].call(value) if stage_options[:convert]
52
68
 
53
- # 这一步转换值。需要注意的是,对象也可能被转换,因为并没有深层次的结构被声明。
69
+ # 第一步,转化值。
70
+ # 需要注意的是,对象也可能被转换,因为并没有深层次的结构被声明。
54
71
  type = stage_options[:type]
55
- unless type.nil? || value.nil?
72
+ unless user_options[:type_conversion] == false || type.nil? || value.nil?
56
73
  begin
57
74
  value = JsonSchema::TypeConverter.convert_value(value, type)
58
75
  rescue JsonSchema::TypeConvertError => e
@@ -60,7 +77,14 @@ module Meta
60
77
  end
61
78
  end
62
79
 
63
- validate!(value, stage_options)
80
+ # 第二步,做校验。
81
+ validate!(value, stage_options) unless user_options[:validation] == false
82
+
83
+ # 第三步,如果使用了 using 块,需要进一步解析
84
+ if stage_options[:using] && stage_options[:using].is_a?(Hash)
85
+ schema = stage_options[:using][:resolve].call(value).to_schema
86
+ value = schema.filter(value, user_options)
87
+ end
64
88
 
65
89
  value
66
90
  end
@@ -89,6 +113,12 @@ module Meta
89
113
  schema[:type] = stage_options[:type] if stage_options[:type]
90
114
  schema[:description] = stage_options[:description] if stage_options[:description]
91
115
  schema[:enum] = stage_options[:allowable] if stage_options[:allowable]
116
+ if stage_options[:using] && stage_options[:using].is_a?(Hash)
117
+ using_options = stage_options[:using]
118
+ schema[:oneOf] = using_options[:one_of].map do |schema|
119
+ schema.to_schema.to_schema_doc(user_options)
120
+ end if using_options[:one_of]
121
+ end
92
122
 
93
123
  schema
94
124
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../utils/kwargs'
4
+
3
5
  module Meta
4
6
  module JsonSchema
5
7
  class ObjectSchema < BaseSchema
@@ -93,8 +95,12 @@ module Meta
93
95
  # > 则该 Schema 生成文档时会使用 $ref 格式。除非 to_ref 选项设置为 false.
94
96
  #
95
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
+ )
102
+
96
103
  stage = user_options[:stage]
97
- # HACK: 标准化选项的工作进行得怎样?
98
104
  locked_scopes = (locked_options || {})[:scope] || []
99
105
  locked_scopes = [locked_scopes] unless locked_scope.nil? && locked_scopes.is_a?(Array)
100
106
  schema_name = @schema_name_resolver.call(stage, locked_scopes)
@@ -8,7 +8,7 @@ module Meta
8
8
  required: false
9
9
  }
10
10
  @allowable_options = (
11
- %i[type description in value using default presenter convert scope items] +
11
+ %i[type description in value using default presenter convert scope items using] +
12
12
  @default_options.keys +
13
13
  JsonSchema::Validators.keys
14
14
  ).uniq
@@ -35,6 +35,8 @@ module Meta
35
35
  # 只要 options 中设置为 nil 的选项没有明确的意义,则下行代码是永远有效的
36
36
  options = (@default_options.compact).merge(options.compact)
37
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)
38
40
  if options[:using]
39
41
  if options[:type].nil?
40
42
  options[:type] = 'object'
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ # 运行时检查关键字参数。
4
+ #
5
+ # 在 Ruby 3 中,关键字参数有所变化。简单来说,关键字参数和 Hash 类型不再自动转化,并且一般情况下推荐使用关键字参数。
6
+ # 但关键字参数还是有稍稍不足之处,比如在做一些复杂的关键字参数定义时。
7
+ #
8
+ # 这个文件编写了一个方法,帮助我们在运行时检查关键字参数。这样,我们就可以像下面这样笼统的方式定义参数,不必用明确的关
9
+ # 键字参数名称。
10
+ #
11
+ # def method_name(x, y, z, **kwargs); end
12
+ # def method_name(x, y, z, kwargs={}); end
13
+ #
14
+ # 使用示例:
15
+ #
16
+ # # 返回 { x: 1, y: 2, z: 3 }
17
+ # Meta::Utils::KeywordArgs.check(args: { x: 1, y: 2 }, schema: [:x, :y, { z: 3 }])
18
+ #
19
+ # # 返回 { x: 1, y: 2, z: 4 }
20
+ # Meta::Utils::KeywordArgs.check(args: { x: 1, y: 2, z: 4 }, schema: [:x, :y, { z: 3 }])
21
+ #
22
+ # # Error: `x` is required
23
+ # Meta::Utils::KeywordArgs.check(args: { y: 2, z: 3 }, schema: [:x, :y, { z: 3 }])
24
+ #
25
+ # # Error: `a` is not allowed
26
+ # Meta::Utils::KeywordArgs.check(args: { a: 1, y: 2, z: 3 }, schema: [:x, :y, { z: 3 }])
27
+
28
+ module Meta
29
+ module Utils
30
+ class KeywordArgs
31
+ class << self
32
+ def check(args:, schema:)
33
+ schemas = build_schemas(schema)
34
+
35
+ # 不接受额外的关键字参数
36
+ extras = args.keys - schemas.keys
37
+ raise "不接受额外的关键字参数:#{extras.join(', ')}" unless extras.empty?
38
+
39
+ # 通过 schema 导出关键字参数
40
+ missing = []
41
+ result = schemas.map do |name, spec|
42
+ if args.include?(name)
43
+ [name, args[name]]
44
+ elsif spec.include?(:default)
45
+ [name, spec[:default]]
46
+ else
47
+ missing << name
48
+ end
49
+ end.to_h
50
+
51
+ # 检查以上导出过程中是否找到缺失的参数
52
+ if missing.empty?
53
+ result
54
+ else
55
+ raise "缺失必要的关键字参数:#{missing.join(', ')}"
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def build_schemas(spec)
62
+ if spec.is_a?(Array)
63
+ build_schemas_from_array(spec)
64
+ elsif spec.is_a?(Hash)
65
+ build_schemas_from_hash(spec)
66
+ elsif spec.is_a?(Symbol)
67
+ build_schemas_from_symbol(spec)
68
+ else
69
+ raise "未知的参数类型:#{spec.class}"
70
+ end
71
+ end
72
+
73
+ def build_schemas_from_array(spec_array)
74
+ spec_array.inject({}) do |accumulated, val|
75
+ accumulated.merge!(build_schemas(val))
76
+ end
77
+ end
78
+
79
+ def build_schemas_from_hash(spec_hash)
80
+ spec_hash.transform_values do |val|
81
+ { default: val }
82
+ end
83
+ end
84
+
85
+ def build_schemas_from_symbol(spec_symbol)
86
+ { spec_symbol => {} }
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
data/meta-api.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "meta-api"
3
- spec.version = "0.0.1"
3
+ spec.version = "0.0.2"
4
4
  spec.authors = ["yetrun"]
5
5
  spec.email = ["yetrun@foxmail.com"]
6
6
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: meta-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - yetrun
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-02 00:00:00.000000000 Z
11
+ date: 2023-03-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: 一个 Web API 框架,该框架采用定义元信息的方式编写 API,并同步生成 API 文档
14
14
  email:
@@ -20,6 +20,7 @@ files:
20
20
  - ".autoenv.zsh"
21
21
  - ".gitignore"
22
22
  - ".rubocop.yml"
23
+ - CHANGELOG.md
23
24
  - Gemfile
24
25
  - Gemfile.lock
25
26
  - LICENSE.txt
@@ -41,6 +42,7 @@ files:
41
42
  - lib/meta/application/meta.rb
42
43
  - lib/meta/application/path_matching_mod.rb
43
44
  - lib/meta/application/route.rb
45
+ - lib/meta/config.rb
44
46
  - lib/meta/entity.rb
45
47
  - lib/meta/errors.rb
46
48
  - lib/meta/json_schema/builders/array_schema_builder.rb
@@ -65,6 +67,7 @@ files:
65
67
  - lib/meta/route_dsl/route_builder.rb
66
68
  - lib/meta/route_dsl/uniformed_params_builder.rb
67
69
  - lib/meta/swagger_doc.rb
70
+ - lib/meta/utils/kwargs.rb
68
71
  - lib/meta/utils/path.rb
69
72
  - meta-api.gemspec
70
73
  homepage: https://github.com/yetrun/web-frame
@@ -74,7 +77,7 @@ metadata:
74
77
  allowed_push_host: https://rubygems.org
75
78
  homepage_uri: https://github.com/yetrun/web-frame
76
79
  source_code_uri: https://github.com/yetrun/web-frame.git
77
- post_install_message:
80
+ post_install_message:
78
81
  rdoc_options: []
79
82
  require_paths:
80
83
  - lib
@@ -89,8 +92,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
92
  - !ruby/object:Gem::Version
90
93
  version: '0'
91
94
  requirements: []
92
- rubygems_version: 3.3.26
93
- signing_key:
95
+ rubygems_version: 3.3.7
96
+ signing_key:
94
97
  specification_version: 4
95
98
  summary: 一个 Web API 框架
96
99
  test_files: []