Class: Taro::Export::OpenAPIv3
- Defined in:
- lib/taro/export/open_api_v3.rb
Overview
rubocop:disable Metrics/ClassLength
Instance Attribute Summary collapse
-
#schemas ⇒ Object
readonly
Returns the value of attribute schemas.
Attributes inherited from Base
Instance Method Summary collapse
- #assert_unique_openapi_name(type) ⇒ Object
- #call(declarations:, title:, version:) ⇒ Object
- #custom_scalar_type?(type) ⇒ Boolean
- #custom_scalar_type_details(scalar) ⇒ Object
- #enum_type_details(enum) ⇒ Object
- #export_complex_field_ref(field) ⇒ Object
- #export_field(field) ⇒ Object
- #export_parameter(field) ⇒ Object
- #export_paths(declarations) ⇒ Object
- #export_route(route, declaration) ⇒ Object
- #export_scalar_field(field) ⇒ Object
- #export_type(type) ⇒ Object
- #extract_component_ref(type) ⇒ Object
- #field_metadata(field) ⇒ Object
-
#initialize ⇒ OpenAPIv3
constructor
A new instance of OpenAPIv3.
- #list_type_details(list) ⇒ Object
- #object_type_details(type) ⇒ Object
- #path_parameters(declaration, route) ⇒ Object
- #query_parameters(declaration, route) ⇒ Object
- #request_body(declaration, route) ⇒ Object
- #request_body_schema(type, use_refs:) ⇒ Object
- #responses(declaration) ⇒ Object
- #route_parameters(declaration, route) ⇒ Object
- #type_details(type) ⇒ Object
- #validate_path_or_query_parameter(field) ⇒ Object
Methods inherited from Base
Constructor Details
#initialize ⇒ OpenAPIv3
Returns a new instance of OpenAPIv3.
4 5 6 7 |
# File 'lib/taro/export/open_api_v3.rb', line 4 def initialize super @schemas = {} end |
Instance Attribute Details
#schemas ⇒ Object (readonly)
Returns the value of attribute schemas.
2 3 4 |
# File 'lib/taro/export/open_api_v3.rb', line 2 def schemas @schemas end |
Instance Method Details
#assert_unique_openapi_name(type) ⇒ Object
240 241 242 243 244 245 246 247 248 249 |
# File 'lib/taro/export/open_api_v3.rb', line 240 def assert_unique_openapi_name(type) @name_to_type_map ||= {} if (prev = @name_to_type_map[type.openapi_name]) && !prev.equivalent?(type) raise Taro::InvariantError, <<~MSG Duplicate openapi_name "#{type.openapi_name}" for types #{prev} and #{type} MSG else @name_to_type_map[type.openapi_name] = type end end |
#call(declarations:, title:, version:) ⇒ Object
9 10 11 12 13 14 15 |
# File 'lib/taro/export/open_api_v3.rb', line 9 def call(declarations:, title:, version:) @result = { openapi: '3.1.0', info: { title:, version: } } paths = export_paths(declarations) @result[:paths] = paths.sort.to_h if paths.any? @result[:components] = { schemas: schemas.sort.to_h } if schemas.any? self end |
#custom_scalar_type?(type) ⇒ Boolean
138 139 140 |
# File 'lib/taro/export/open_api_v3.rb', line 138 def custom_scalar_type?(type) type < Taro::Types::ScalarType && (type.openapi_pattern || type.deprecated) end |
#custom_scalar_type_details(scalar) ⇒ Object
231 232 233 234 235 236 237 238 |
# File 'lib/taro/export/open_api_v3.rb', line 231 def custom_scalar_type_details(scalar) { type: scalar.openapi_type, deprecated: scalar.deprecated, description: scalar.desc, pattern: scalar.openapi_pattern, }.compact end |
#enum_type_details(enum) ⇒ Object
213 214 215 216 217 218 219 220 |
# File 'lib/taro/export/open_api_v3.rb', line 213 def enum_type_details(enum) { type: enum.item_type.openapi_type, deprecated: enum.deprecated, description: enum.desc, enum: enum.values, }.compact end |
#export_complex_field_ref(field) ⇒ Object
159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/taro/export/open_api_v3.rb', line 159 def export_complex_field_ref(field) ref = extract_component_ref(field.type) return ref if (field).empty? && !field.null if field.null # RE nullable: https://stackoverflow.com/a/70658334 { oneOf: [ref, { type: 'null' }] } else # i.e. with metadata such as description or deprecated # https://github.com/OAI/OpenAPI-Specification/issues/2033 { allOf: [ref] } end.merge((field)) end |
#export_field(field) ⇒ Object
142 143 144 145 146 147 148 |
# File 'lib/taro/export/open_api_v3.rb', line 142 def export_field(field) if field.type < Taro::Types::ScalarType export_scalar_field(field) else export_complex_field_ref(field) end end |
#export_parameter(field) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/taro/export/open_api_v3.rb', line 66 def export_parameter(field) validate_path_or_query_parameter(field) { name: field.name, deprecated: field.deprecated, description: field.desc, required: !field.null, schema: export_field(field).except(:deprecated, :description), }.compact end |
#export_paths(declarations) ⇒ Object
17 18 19 20 21 22 23 24 |
# File 'lib/taro/export/open_api_v3.rb', line 17 def export_paths(declarations) declarations.sort.each_with_object({}) do |declaration, paths| declaration.routes.each do |route| paths[route.openapi_path] ||= {} paths[route.openapi_path].merge! export_route(route, declaration) end end end |
#export_route(route, declaration) ⇒ Object
26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/taro/export/open_api_v3.rb', line 26 def export_route(route, declaration) { route.verb.to_sym => { description: declaration.desc, summary: declaration.summary, tags: declaration., operationId: route.openapi_operation_id, parameters: route_parameters(declaration, route), requestBody: request_body(declaration, route), responses: responses(declaration), }.compact, } end |
#export_scalar_field(field) ⇒ Object
150 151 152 153 154 155 156 157 |
# File 'lib/taro/export/open_api_v3.rb', line 150 def export_scalar_field(field) base = { type: field.openapi_type } # Using oneOf seems more correct than an array of types # as it puts props like format together with the main type. # https://github.com/OAI/OpenAPI-Specification/issues/3148 base = { oneOf: [base, { type: 'null' }] } if field.null base.merge((field)) end |
#export_type(type) ⇒ Object
130 131 132 133 134 135 136 |
# File 'lib/taro/export/open_api_v3.rb', line 130 def export_type(type) if type < Taro::Types::ScalarType && !custom_scalar_type?(type) { type: type.openapi_type } else extract_component_ref(type) end end |
#extract_component_ref(type) ⇒ Object
181 182 183 184 185 |
# File 'lib/taro/export/open_api_v3.rb', line 181 def extract_component_ref(type) assert_unique_openapi_name(type) schemas[type.openapi_name.to_sym] ||= type_details(type) { '$ref': "#/components/schemas/#{type.openapi_name}" } end |
#field_metadata(field) ⇒ Object
172 173 174 175 176 177 178 179 |
# File 'lib/taro/export/open_api_v3.rb', line 172 def (field) = {} [:description] = field.desc if field.desc [:deprecated] = field.deprecated unless field.deprecated.nil? [:default] = field.default if field.default_specified? [:enum] = field.enum if field.enum end |
#list_type_details(list) ⇒ Object
222 223 224 225 226 227 228 229 |
# File 'lib/taro/export/open_api_v3.rb', line 222 def list_type_details(list) { type: 'array', deprecated: list.deprecated, description: list.desc, items: export_type(list.item_type), }.compact end |
#object_type_details(type) ⇒ Object
201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/taro/export/open_api_v3.rb', line 201 def object_type_details(type) required = type.fields.values.reject(&:null).map(&:name) { type: type.openapi_type, deprecated: type.deprecated, description: type.desc, required: (required if required.any?), properties: type.fields.to_h { |name, f| [name, export_field(f)] }, additionalProperties: (true if type.additional_properties?), }.compact end |
#path_parameters(declaration, route) ⇒ Object
44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/taro/export/open_api_v3.rb', line 44 def path_parameters(declaration, route) route.path_params.map do |param_name| param_field = declaration.params.fields[param_name] || raise( Taro::InvariantError, "Declaration missing for path param #{param_name} of route #{route}" ) # path params are always required in rails export_parameter(param_field).merge(in: 'path', required: true) end end |
#query_parameters(declaration, route) ⇒ Object
56 57 58 59 60 61 62 63 64 |
# File 'lib/taro/export/open_api_v3.rb', line 56 def query_parameters(declaration, route) return [] if route.can_have_request_body? declaration.params.fields.filter_map do |name, param_field| next if route.path_params.include?(name) export_parameter(param_field).merge(in: 'query') end end |
#request_body(declaration, route) ⇒ Object
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/taro/export/open_api_v3.rb', line 86 def request_body(declaration, route) return unless route.can_have_request_body? params = declaration.params body_param_fields = params.fields.reject do |name, _field| route.path_params.include?(name) end return unless body_param_fields.any? body_input_type = Class.new(params) body_input_type.fields.replace(body_param_fields) body_input_type.openapi_name = "#{route.openapi_operation_id}_Input" # For polymorphic routes (more than one for the same declaration), # we can't use refs because their request body might differ: # Different params might be in the path vs. in the request body. use_refs = !declaration.polymorphic_route? schema = request_body_schema(body_input_type, use_refs:) { content: { 'application/json': { schema: } } } end |
#request_body_schema(type, use_refs:) ⇒ Object
107 108 109 110 111 112 113 |
# File 'lib/taro/export/open_api_v3.rb', line 107 def request_body_schema(type, use_refs:) if use_refs extract_component_ref(type) else type_details(type) end end |
#responses(declaration) ⇒ Object
115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/taro/export/open_api_v3.rb', line 115 def responses(declaration) declaration.returns.sort.to_h do |code, type| # response description is required in openapi 3 – fall back to status code description = declaration.return_descriptions[code] || type.desc || Taro::StatusCode.(code) [ code.to_s, { description:, content: { 'application/json': { schema: export_type(type) } }, } ] end end |
#route_parameters(declaration, route) ⇒ Object
40 41 42 |
# File 'lib/taro/export/open_api_v3.rb', line 40 def route_parameters(declaration, route) path_parameters(declaration, route) + query_parameters(declaration, route) end |
#type_details(type) ⇒ Object
187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/taro/export/open_api_v3.rb', line 187 def type_details(type) if type.respond_to?(:fields) # InputType or ObjectType object_type_details(type) elsif type < Taro::Types::EnumType enum_type_details(type) elsif type < Taro::Types::ListType list_type_details(type) elsif custom_scalar_type?(type) custom_scalar_type_details(type) else raise Taro::InvariantError, "Unexpected type: #{type}" end end |
#validate_path_or_query_parameter(field) ⇒ Object
78 79 80 81 82 83 84 |
# File 'lib/taro/export/open_api_v3.rb', line 78 def validate_path_or_query_parameter(field) ok = %i[string integer] ok.include?(field.type.openapi_type) || raise(Taro::ArgumentError, <<~MSG) Unsupported #{field.openapi_type} as path/query param "#{field.name}", expected one of: #{ok.join(', ')} MSG end |