svelte 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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