svelte 0.1.1

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.
@@ -0,0 +1,9 @@
1
+ module Svelte
2
+ # Svelte error class to represent version errors.
3
+ # Raised when a Swagger v1 JSON is fed into Svelte
4
+ class VersionError < StandardError
5
+ def message
6
+ 'Invalid Swagger version spec supplied. Svelte supports Swagger v2 only'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,63 @@
1
+ module Svelte
2
+ # Class that handles the actual execution of dynamically generated operations
3
+ # Each created operation will eventually call this class in order to make the
4
+ # final HTTP request to the REST endpoint
5
+ class GenericOperation
6
+ class << self
7
+ # Make an HTTP request to a REST resource
8
+ # @param verb [String] http verb to use, i.e. `'get'`
9
+ # @param path [Path] Path object containing information about the
10
+ # operation to be called
11
+ # @param configuration [Configuration] Swagger API configuration
12
+ # @param parameters [Hash] payload of the request, i.e. `{ petId: 1}`
13
+ # @param options [Hash] request options, i.e. `{ timeout: 10 }`
14
+ def call(verb:, path:, configuration:, parameters:, options:)
15
+ url = url_for(configuration: configuration,
16
+ path: path,
17
+ parameters: parameters)
18
+ request_parameters = clean_parameters(path: path,
19
+ parameters: parameters)
20
+ RestClient.call(verb: verb,
21
+ url: url,
22
+ params: request_parameters,
23
+ options: options)
24
+ end
25
+
26
+ private
27
+
28
+ def url_for(configuration:, path:, parameters:)
29
+ url_path =
30
+ [
31
+ path.non_parameter_elements,
32
+ named_parameters(path: path, parameters: parameters)
33
+ ].flatten.join('/')
34
+
35
+ protocol = configuration.protocol
36
+ host = configuration.host
37
+ base_path = configuration.base_path
38
+ if base_path == '/'
39
+ base_path = ''
40
+ end
41
+ "#{protocol}://#{host}#{base_path}/#{url_path}"
42
+ end
43
+
44
+ def named_parameters(path:, parameters:)
45
+ path.parameter_elements.map do |parameter_element|
46
+ unless parameters.key?(parameter_element)
47
+ raise ParameterError,
48
+ "Required parameter `#{parameter_element}` missing"
49
+ end
50
+ parameters[parameter_element]
51
+ end
52
+ end
53
+
54
+ def clean_parameters(path:, parameters:)
55
+ clean_parameters = parameters.dup
56
+ path.parameter_elements.each do |parameter_element|
57
+ clean_parameters.delete(parameter_element)
58
+ end
59
+ clean_parameters
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,171 @@
1
+ require 'svelte/model_factory/parameter'
2
+
3
+ module Svelte
4
+ # Bridging the gap between an application and any external service that
5
+ # publishes its API as a Swagger JSON spec.
6
+ # @note This module is supposed to be extended not used directly
7
+ module ModelFactory
8
+ # Creates typed Ruby objects from JSON definitions. These definitions are
9
+ # found in the Swagger JSON spec as a top-level key, "definitions".
10
+ # @param json [Hash] hash of a swagger models definition
11
+ # @return [Hash] A hash of model names to models created
12
+ def define_models(json)
13
+ return unless json
14
+ models = {}
15
+ model_definitions = json['definitions']
16
+ model_definitions.each do |model_name, parameters|
17
+ attributes = parameters['properties'].keys
18
+ model = Class.new do
19
+ attr_reader(*attributes.map(&:to_sym))
20
+
21
+ parameters['properties'].each do |attribute, options|
22
+ define_method("#{attribute}=", lambda do |value|
23
+ if public_send(attribute).nil? || !public_send(attribute).present?
24
+ permitted_values = options.fetch('enum', [])
25
+ required = parameters.fetch('required', []).include?(attribute)
26
+ instance_variable_set(
27
+ "@#{attribute}",
28
+ Parameter.new(options['type'],
29
+ permitted_values: permitted_values,
30
+ required: required)
31
+ )
32
+ end
33
+
34
+ instance_variable_get("@#{attribute}").value = value
35
+ end)
36
+ end
37
+ end
38
+
39
+ define_initialize_on(model: model)
40
+ define_attributes_on(model: model)
41
+ define_required_attributes_on(model: model)
42
+ define_json_for_model_on(model: model)
43
+ define_nested_models_on(model: model)
44
+ define_as_json_on(model: model)
45
+ define_to_json_on(model: model)
46
+ define_validate_on(model: model)
47
+ define_valid_on(model: model)
48
+
49
+ model.instance_variable_set('@json_for_model', parameters.freeze)
50
+
51
+ models[model_name] = model
52
+ end
53
+
54
+ models.each do |model_name, model|
55
+ const_set(model_name, model)
56
+ end
57
+ end
58
+
59
+ # Creates typed Ruby objects from JSON String definitions. These definitions
60
+ # are found in the Swagger JSON spec as a top-level key, "models".
61
+ # @param string [String] string of json for a swagger models definition
62
+ # @return [Hash] A hash of model names to models created
63
+ def define_models_from_json_string(string)
64
+ define_models(JSON.parse(string)) if string
65
+ end
66
+
67
+ # Creates typed Ruby objects from JSON File definitions. These definitions
68
+ # are found in the Swagger JSON spec as a top-level key, "models".
69
+ # @param file [String] path to a json file for a swagger models definition
70
+ # @return [Hash] A hash of model names to models created
71
+ def define_models_from_file(file)
72
+ define_models_from_json_string(File.read(file)) if file
73
+ end
74
+
75
+ private
76
+
77
+ def define_initialize_on(model:)
78
+ model.send(:define_method, :initialize, lambda do
79
+ (required_attributes - nested_models.keys).each do |required|
80
+ public_send("#{required}=", Parameter::UNSET)
81
+ end
82
+ required_nested_models = nested_models.keys & required_attributes
83
+ nested_models.each do |nested_model_name, nested_model_info|
84
+ return unless required_nested_models.include?(nested_model_name)
85
+ nested_class_name = public_send("#{nested_model_name}=",
86
+ nested_model_info['$ref']
87
+ .split('/').last)
88
+ nested_class_name = self.class.name.gsub(/::[^:]*\z/, '::') +
89
+ nested_class_name
90
+ public_send("#{nested_model_name}=",
91
+ Object.const_get(nested_class_name).new)
92
+ end
93
+ end)
94
+ end
95
+
96
+ def define_validate_on(model:)
97
+ model.send(:define_method, :validate, lambda do
98
+ invalid_params = {}
99
+ attributes.each do |attribute|
100
+ if public_send(attribute).respond_to?(:validate)
101
+ result = public_send(attribute).validate
102
+ invalid_params[attribute] = result unless result.empty?
103
+ end
104
+ end
105
+ invalid_params
106
+ end)
107
+ end
108
+
109
+ def define_valid_on(model:)
110
+ model.send(:define_method, :valid?, lambda do
111
+ validate.empty?
112
+ end)
113
+ end
114
+
115
+ def define_to_json_on(model:)
116
+ model.send(:define_method, :to_json, lambda do
117
+ as_json.to_json
118
+ end)
119
+ end
120
+
121
+ def define_attributes_on(model:)
122
+ model.send(:define_method, :attributes, lambda do
123
+ @attributes ||= json_for_model['properties'].keys
124
+ end)
125
+ end
126
+
127
+ def define_as_json_on(model:)
128
+ model.send(:define_method, :as_json, lambda do
129
+ structure = {}
130
+ attributes.each do |attribute|
131
+ value = if public_send(attribute).respond_to?(:as_json)
132
+ public_send(attribute).as_json
133
+ else
134
+ public_send(attribute)
135
+ end
136
+
137
+ structure[attribute] = value unless value.nil?
138
+ end
139
+
140
+ if structure.empty?
141
+ nil
142
+ else
143
+ symbolised_structure = {}
144
+ structure.each do |key, value|
145
+ symbolised_structure[key.to_sym] = value
146
+ end
147
+ symbolised_structure
148
+ end
149
+ end)
150
+ end
151
+
152
+ def define_json_for_model_on(model:)
153
+ model.send(:define_method, :json_for_model, lambda do
154
+ self.class.instance_variable_get('@json_for_model')
155
+ end)
156
+ end
157
+
158
+ def define_nested_models_on(model:)
159
+ model.send(:define_method, :nested_models, lambda do
160
+ json_for_model['properties']
161
+ .select { |_property, sub_properties| sub_properties.key?('$ref') }
162
+ end)
163
+ end
164
+
165
+ def define_required_attributes_on(model:)
166
+ model.send(:define_method, :required_attributes, lambda do
167
+ @required_attributes ||= json_for_model.fetch('required', [])
168
+ end)
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,154 @@
1
+ module Svelte
2
+ module ModelFactory
3
+ # Helper class to wrap around all parameters
4
+ class Parameter
5
+
6
+ # Constant to represent an unset parameter
7
+ UNSET = Class.new
8
+
9
+ # Override of the `inspect` method to return a string representation
10
+ # of the class
11
+ def UNSET.inspect
12
+ 'unset'
13
+ end
14
+
15
+ attr_reader :type
16
+ attr_accessor :value
17
+
18
+ # Creates a new Parameter
19
+ # @param type [String]: Type of the parameter, i.e. `'integer'`
20
+ # @param permitted_values [Array]: array of allowed values
21
+ # for the parameter
22
+ # @param required [Boolean]: is the parameter required?
23
+ def initialize(type, permitted_values: [], required: false)
24
+ @type = type
25
+ @permitted_values = permitted_values
26
+ @required = required
27
+ @value = UNSET
28
+ end
29
+
30
+ # @return [Boolean] true if and only if the parameter is valid
31
+ def valid?
32
+ validate.empty?
33
+ end
34
+
35
+ # @return [String] String representing
36
+ # the validation errors of the parameter
37
+ def validate
38
+ # We are not a required parameter, so being unset is fine.
39
+ return '' if validate_blank
40
+
41
+ # if we have a nested model
42
+ return value.validate if value.respond_to?(:validate)
43
+
44
+ messages = validate_messages
45
+ messages.any? ? 'Invalid parameter: ' + messages.join(', ') : ''
46
+ end
47
+
48
+ # @return [Boolean] true if and only if the parameter has been set
49
+ def present?
50
+ !unset?
51
+ end
52
+
53
+ # @return [Hash] json representation of the parameter
54
+ def as_json
55
+ value.respond_to?(:as_json) ? value.as_json : value if present?
56
+ end
57
+
58
+ private
59
+
60
+ def validate_messages
61
+ messages = []
62
+
63
+ validate_type(messages)
64
+
65
+ messages << invalid_type_enum_message unless validate_value_in_enum
66
+ messages << required_parameter_missing_message unless validate_required
67
+
68
+ messages
69
+ end
70
+
71
+ def unset?
72
+ value == UNSET
73
+ end
74
+
75
+ def validate_blank
76
+ if @required
77
+ false
78
+ else
79
+ unset?
80
+ end
81
+ end
82
+
83
+ def validate_required
84
+ return true unless @required
85
+ return false if unset?
86
+ true
87
+ end
88
+
89
+ def validate_value_in_enum
90
+ return true if @permitted_values.empty?
91
+ @permitted_values.include?(value)
92
+ end
93
+
94
+ def validate_type(messages)
95
+ return true if unset? || not_required_but_nil?
96
+
97
+ # TODO: this smells, should we have a duck type that responds
98
+ # to .validate?
99
+ valid = case type
100
+ when 'string'
101
+ validate_string
102
+ when 'boolean'
103
+ validate_boolean
104
+ when 'number', 'integer'
105
+ validate_number
106
+ when 'array'
107
+ validate_array
108
+ when 'object'
109
+ # Objects cannot be validated
110
+ true
111
+ else
112
+ false
113
+ end
114
+ messages << invalid_type_message unless valid
115
+ end
116
+
117
+ def invalid_type_message
118
+ "Expected valid #{type}, but was #{value.inspect}"
119
+ end
120
+
121
+ def invalid_type_enum_message
122
+ "Expected one of #{@permitted_values.inspect}, but was #{value.inspect}"
123
+ end
124
+
125
+ def required_parameter_missing_message
126
+ 'Missing required parameter'
127
+ end
128
+
129
+ def not_required_but_nil?
130
+ !@required && value.nil?
131
+ end
132
+
133
+ def validate_string
134
+ value.is_a?(String)
135
+ end
136
+
137
+ def validate_array_contents
138
+ value.all? { |v| !v.respond_to?(:valid?) || v.valid? }
139
+ end
140
+
141
+ def validate_array
142
+ value.is_a?(Array) && validate_array_contents
143
+ end
144
+
145
+ def validate_boolean
146
+ value == !!value
147
+ end
148
+
149
+ def validate_number
150
+ value.is_a?(Numeric)
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,31 @@
1
+ module Svelte
2
+ # Describes a Swagger API Operation
3
+ class Operation
4
+ attr_reader :verb, :properties, :path
5
+
6
+ # Creates a new Operation.
7
+ # @param verb [String] operation verb i.e. `'get'`
8
+ # @param properties [Hash] definition
9
+ # @param path [Path] Path the operation belongs to
10
+ def initialize(verb:, properties:, path:)
11
+ @verb = verb
12
+ @properties = properties
13
+ @path = path
14
+ validate
15
+ end
16
+
17
+ # Operation identifier
18
+ # @return [String] unique Swagger API operation identifier
19
+ def id
20
+ properties['operationId']
21
+ end
22
+
23
+ private
24
+
25
+ def validate
26
+ unless id.is_a?(String)
27
+ raise JSONError, 'Operation is missing mandatory `operationId` field'
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,47 @@
1
+ module Svelte
2
+ # Dynamically builds Swagger API operations on top of a given module
3
+ class OperationBuilder
4
+ class << self
5
+ # Builds an operation on top of `module_constant`
6
+ # @param operation [Svete::Operation] operation to build
7
+ # @param module_constant [Module] operation to build
8
+ # @param configuration [Configuration] Swagger API configuration
9
+ def build(operation:, module_constant:, configuration:)
10
+ builder = self
11
+ method_name = StringManipulator.method_name_for(operation.id)
12
+ module_constant.define_singleton_method(method_name) do |*parameters|
13
+ GenericOperation.call(
14
+ verb: operation.verb,
15
+ path: operation.path,
16
+ configuration: configuration,
17
+ parameters: builder.request_parameters(full_parameters: parameters),
18
+ options: builder.options(full_parameters: parameters))
19
+ end
20
+ end
21
+
22
+ # Returns the parameters that are to be sent as part of the request
23
+ # to the API endpoint.
24
+ # @param full_parameters [Array] array with the arguments passed into the
25
+ # method call
26
+ # @return [Hash] Hash with all the parameters to be sent as part of the
27
+ # request
28
+ # @note All keys will be transformed from `Symbol` to `String`
29
+ def request_parameters(full_parameters:)
30
+ return {} if full_parameters.compact.empty?
31
+ full_parameters.first.inject({}) do |memo, (k, v)|
32
+ memo.merge!(k.to_s => v)
33
+ end
34
+ end
35
+
36
+ # Returns the options that are to be sent as part of the request
37
+ # to the API endpoint.
38
+ # @param full_parameters [Array] array with the arguments passed into the
39
+ # method call
40
+ # @return [Hash] Hash with all the options to be sent as part of the
41
+ # request
42
+ def options(full_parameters:)
43
+ full_parameters[1] || {}
44
+ end
45
+ end
46
+ end
47
+ end