Class: Apiwork::Export::Base

Inherits:
Object
  • Object
show all
Includes:
Configurable
Defined in:
lib/apiwork/export/base.rb

Overview

Base class for exports.

Subclass this to create custom export formats. Declare output type and override ‘#generate` to produce output.

Examples:

Hash export (supports json/yaml)

class OpenAPI < Apiwork::Export::Base
  export_name :openapi
  output :hash

  def generate
    { openapi: '3.1.0', ... }  # Returns Hash
  end
end

String export (fixed format)

class ProtobufExport < Apiwork::Export::Base
  export_name :protobuf
  output :string
  file_extension '.proto'

  def generate
    "syntax = \"proto3\";\n..."  # Returns String
  end
end

# Register the export
Apiwork::Export.register(ProtobufExport)

Direct Known Subclasses

Apiwork, OpenAPI, Sorbus, TypeScript, Zod

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Configurable

define, option

Constructor Details

#initialize(api_base_path, key_format: nil, locale: nil, **options) ⇒ Base

Returns a new instance of Base.



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/apiwork/export/base.rb', line 203

def initialize(api_base_path, key_format: nil, locale: nil, **options)
  @api_base_path = api_base_path
  @api_class = API.find!(api_base_path)

  unless @api_class.export_configs.key?(self.class.export_name)
    raise ConfigurationError,
          "Export :#{self.class.export_name} is not declared for #{api_base_path}. " \
          "Add `export :#{self.class.export_name}` to your API definition."
  end

  config = @api_class.export_configs[self.class.export_name]
  api_config = extract_options_from_config(config)
  all_options = options.merge(key_format:, locale:).compact
  @options = self.class.default_options.merge(api_config).merge(all_options)
  @options[:key_format] ||= @api_class.key_format || :keep
  validate_options!

  @api = @api_class.introspect(locale: @options[:locale])
end

Class Attribute Details

.output_typeObject (readonly)



94
95
96
# File 'lib/apiwork/export/base.rb', line 94

def output_type
  @output_type
end

Instance Attribute Details

#apiIntrospection::API (readonly)

The API introspection for this export.

Primary interface for accessing introspection data in export generators.

Returns:

See Also:



48
49
50
# File 'lib/apiwork/export/base.rb', line 48

def api
  @api
end

#api_base_pathObject (readonly)



48
49
50
# File 'lib/apiwork/export/base.rb', line 48

def api_base_path
  @api_base_path
end

#optionsObject (readonly)



48
49
50
# File 'lib/apiwork/export/base.rb', line 48

def options
  @options
end

Class Method Details

.content_type_for(format: nil) ⇒ Object



130
131
132
133
134
135
136
137
138
# File 'lib/apiwork/export/base.rb', line 130

def content_type_for(format: nil)
  resolved = format || :json

  if hash_output?
    resolved == :yaml ? 'application/yaml' : 'application/json'
  else
    'text/plain; charset=utf-8'
  end
end

.export_name(name = nil) ⇒ Symbol?

The name for this export.

Parameters:

  • name (Symbol, nil) (defaults to: nil)

    (nil) The export name.

Returns:

  • (Symbol, nil)


59
60
61
62
# File 'lib/apiwork/export/base.rb', line 59

def export_name(name = nil)
  @export_name = name.to_sym if name
  @export_name
end

.extract_nested_option(option, value) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/apiwork/export/base.rb', line 157

def extract_nested_option(option, value)
  return nil unless value.is_a?(Hash)

  nested = {}
  option.children.each do |child_name, child_option|
    child_value = value[child_name] || value[child_name.to_s]
    next if child_value.nil?

    nested[child_name] = child_option.cast(child_value)
  end
  nested
end

.extract_nested_option_from_env(parent_name, option) ⇒ Object



187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/apiwork/export/base.rb', line 187

def extract_nested_option_from_env(parent_name, option)
  nested = {}
  prefix = parent_name.to_s.upcase

  option.children.each do |child_name, child_option|
    env_key = "#{prefix}_#{child_name.to_s.upcase}"
    value = ENV[env_key]
    next if value.nil?

    nested[child_name] = child_option.cast(value)
  end

  nested
end

.extract_options(source) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/apiwork/export/base.rb', line 140

def extract_options(source)
  result = {}

  options.each do |name, option|
    value = source[name] || source[name.to_s]
    next if value.nil?

    result[name] = if option.nested?
                     extract_nested_option(option, value)
                   else
                     option.cast(value)
                   end
  end

  result
end

.extract_options_from_envObject



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/apiwork/export/base.rb', line 170

def extract_options_from_env
  result = {}

  options.each do |name, option|
    if option.nested?
      nested = extract_nested_option_from_env(name, option)
      result[name] = nested if nested.any?
    else
      env_key = name.to_s.upcase
      value = ENV[env_key]
      result[name] = option.cast(value) unless value.nil?
    end
  end

  result
end

.file_extension(value = nil) ⇒ String?

The file extension for this export.

Only applies to string exports. Hash exports derive extension from format.

Parameters:

  • value (String, nil) (defaults to: nil)

    (nil) The file extension (e.g., ‘.ts’).

Returns:

  • (String, nil)

Raises:



86
87
88
89
90
91
92
# File 'lib/apiwork/export/base.rb', line 86

def file_extension(value = nil)
  return @file_extension unless value

  raise ConfigurationError, 'file_extension not allowed for output :hash exports' if output_type == :hash

  @file_extension = value
end

.file_extension_for(format: nil) ⇒ Object



120
121
122
123
124
125
126
127
128
# File 'lib/apiwork/export/base.rb', line 120

def file_extension_for(format: nil)
  resolved = format || :json

  if hash_output?
    resolved == :yaml ? '.yaml' : '.json'
  else
    file_extension
  end
end

.generate(api_base_path, format: nil, **options) ⇒ Object

Raises:

  • (ArgumentError)


96
97
98
99
100
101
102
103
104
# File 'lib/apiwork/export/base.rb', line 96

def generate(api_base_path, format: nil, **options)
  format ||= :json

  raise ArgumentError, "#{export_name} export does not support #{format} format" if hash_output? && !supports_format?(format)

  export = new(api_base_path, **options)
  content = export.generate
  export.serialize(content, format:)
end

.hash_output?Boolean

Returns:

  • (Boolean)


106
107
108
# File 'lib/apiwork/export/base.rb', line 106

def hash_output?
  output_type == :hash
end

.output(type = nil) ⇒ Symbol?

The output for this export.

Parameters:

  • type (Symbol, nil) (defaults to: nil)

    (nil) [:hash, :string] The output type. :hash for Hash output (json/yaml), :string for String output.

Returns:

  • (Symbol, nil)

Raises:

  • (ArgumentError)


70
71
72
73
74
75
76
# File 'lib/apiwork/export/base.rb', line 70

def output(type = nil)
  return @output_type unless type

  raise ArgumentError, "output must be :hash or :string, got #{type.inspect}" unless %i[hash string].include?(type)

  @output_type = type
end

.string_output?Boolean

Returns:

  • (Boolean)


110
111
112
# File 'lib/apiwork/export/base.rb', line 110

def string_output?
  output_type == :string
end

.supports_format?(format) ⇒ Boolean

Returns:

  • (Boolean)


114
115
116
117
118
# File 'lib/apiwork/export/base.rb', line 114

def supports_format?(format)
  return true if hash_output? && %i[json yaml].include?(format)

  false
end

Instance Method Details

#extract_options_from_config(config) ⇒ Object



265
266
267
# File 'lib/apiwork/export/base.rb', line 265

def extract_options_from_config(config)
  config.to_h.slice(*self.class.options.keys).compact
end

#generateHash, String

Generates the export output.

Override this method in subclasses to produce the export format. Access API data via the #api method which provides typed access to types, enums, resources, actions, and other introspection data.

Returns:

  • (Hash, String)

Raises:

  • (NotImplementedError)

See Also:



232
233
234
# File 'lib/apiwork/export/base.rb', line 232

def generate
  raise NotImplementedError, "#{self.class} must implement #generate"
end

#key_formatSymbol

The key format for this export.

Returns:

  • (Symbol)


240
241
242
# File 'lib/apiwork/export/base.rb', line 240

def key_format
  @options[:key_format]
end

#serialize(content, format:) ⇒ Object



276
277
278
279
280
281
282
283
284
285
# File 'lib/apiwork/export/base.rb', line 276

def serialize(content, format:)
  return content unless content.is_a?(Hash)

  case format
  when :yaml
    content.deep_stringify_keys.to_yaml
  else
    JSON.pretty_generate(content)
  end
end

#transform_key(key) ⇒ String

Transforms a key according to the configured key format.

Parameters:

  • key (String, Symbol)

    The key to transform.

Returns:

  • (String)

See Also:



251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/apiwork/export/base.rb', line 251

def transform_key(key)
  key_string = key.to_s

  return key_string if key_string.match?(/\A[A-Z]+\z/)

  case key_format
  when :camel then key_string.camelize(:lower)
  when :pascal then key_string.camelize
  when :kebab then key_string.dasherize
  when :underscore then key_string.underscore
  else key_string
  end
end

#validate_options!Object



269
270
271
272
273
274
# File 'lib/apiwork/export/base.rb', line 269

def validate_options!
  @options.each do |name, value|
    option = self.class.options[name]
    option&.validate!(value)
  end
end