Class: Marty::Api::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/marty/api/base.rb

Defined Under Namespace

Classes: SchemaValidator

Constant Summary collapse

@@numbers =
{}
@@schemas =
{}

Class Method Summary collapse

Class Method Details

.after_evaluate(api_params, result) ⇒ Object



33
# File 'lib/marty/api/base.rb', line 33

def self.after_evaluate api_params, result; end

.before_evaluate(api_params) ⇒ Object



31
# File 'lib/marty/api/base.rb', line 31

def self.before_evaluate api_params; end

.engine_params_filterObject

api handles



23
24
25
# File 'lib/marty/api/base.rb', line 23

def self.engine_params_filter
  ['password']
end

.evaluate(params, _request, config) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/marty/api/base.rb', line 51

def self.evaluate params, _request, config
  # prevent script evaluation from modifying passed in params
  params = params.deep_dup

  schema_key = [params[:tag], params[:script], params[:node], params[:attr]]
  input_schema = @@schemas[schema_key]
  unless input_schema
    begin
      # get_schema will either return a hash with the schema,
      # or a string with the error
      result_schema = Marty::JsonSchema.get_schema(*schema_key)

      # only store schema in cache when not error
      @@schemas[schema_key] = result_schema if result_schema.is_a?(Hash)
      input_schema = result_schema
    rescue StandardError => e
      return { error: e.message }
    end
  end

  # if schema was found
  if input_schema.is_a?(Hash)
    # fix numbers types
    numbers = @@numbers[schema_key] ||=
      Marty::JsonSchema.get_numbers(input_schema)

    # modify params in place
    Marty::JsonSchema.fix_numbers(params[:params], numbers)
  elsif !input_schema.include?('Schema not defined')
    # else if some error besides schema not defined, fail
    return { error: input_schema }
  end

  # validate input schema
  if config[:input_validated]

    # must fail if schema not found or some other error
    return { "error": input_schema } if input_schema.is_a?(String)

    begin
      res = SchemaValidator.validate_schema(input_schema, params[:params])
    rescue NameError
      return { error: "Unrecognized PgEnum for attribute #{params[:attr]}" }
    rescue StandardError => e
      return { error: "#{params[:attr]}: #{e.message}" }
    end

    schema_errors = SchemaValidator.get_errors(res) unless res.empty?
    return { error: "Error(s) validating: #{schema_errors}" } if
      schema_errors
  end

  # get script engine
  begin
    engine = Marty::ScriptSet.new(params[:tag]).get_engine(params[:script])
  rescue StandardError => e
    error = "Can't get engine: #{params[:script] || 'nil'} with tag: " \
            "#{params[:tag] || 'nil'}; message: #{e.message}"
    Marty::Logger.info error
    return { error: error }
  end

  retval = nil

  # evaluate script
  begin
    if params[:background]
      res = engine.background_eval(params[:node],
                                   params[:params],
                                   params[:attr])

      return retval = { 'job_id' => res.__promise__.id }
    end

    res = engine.evaluate(params[:node],
                          params[:attr],
                          params[:params])

    # validate output schema
    if config[:output_validated] && !(res.is_a?(Hash) && res['error'])
      begin
        output_schema_params = params + { attr: params[:attr] + '_' }
        schema = SchemaValidator.get_schema(output_schema_params)
      rescue StandardError => e
        return { error: e.message }
      end

      begin
        schema_errors = SchemaValidator.validate_schema(schema, res)
      rescue NameError
        return { error: "Unrecognized PgEnum for attribute #{attr}" }
      rescue StandardError => e
        return { error: "#{attr}: #{e.message}" }
      end

      if schema_errors.present?
        errors = schema_errors.map { |e| e[:message] }

        Marty::Logger.error(
          "API #{params[:script]}:#{params[:node]}.#{params[:attr]}",
          error: errors, data: res
        )

        msg = "Error(s) validating: #{errors}"
        res = config[:strict_validate] ? { error: msg, data: res } : res
      end
    end

    # if attr is an array, return result as an array
    retval = params[:return_array] ? [res] : res
  rescue StandardError => e
    msg = Delorean::Engine.grok_runtime_exception(e).symbolize_keys
    Marty::Logger.info "Evaluation error: #{msg}"
    retval = msg
  ensure
    error = retval.is_a?(Hash) ? retval[:error] : nil
  end
end

.filter_hash(hash, filter_params) ⇒ Object



170
171
172
173
174
175
176
# File 'lib/marty/api/base.rb', line 170

def self.filter_hash hash, filter_params
  return unless hash

  pf_class = ::Marty::RailsApp.parameter_filter_class
  pf = pf_class.new(filter_params)
  pf.filter(hash.stringify_keys)
end

.inherited(klass) ⇒ Object



5
6
7
8
# File 'lib/marty/api/base.rb', line 5

def self.inherited(klass)
  @@class_list << klass.to_s
  super
end

.is_authorized?(params) ⇒ Boolean

Returns:

  • (Boolean)


38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/marty/api/base.rb', line 38

def self.is_authorized? params
  is_secured = Marty::ApiAuth.where(
    script_name: params[:script],
    obsoleted_dt: 'infinity'
  ).exists?

  !is_secured || Marty::ApiAuth.where(
    api_key:       params[:api_key],
    script_name:   params[:script],
    obsoleted_dt: 'infinity'
  ).pluck(:app_name).first
end

.log(result, params, request) ⇒ Object



199
200
201
202
# File 'lib/marty/api/base.rb', line 199

def self.log result, params, request
  desc = params.values_at(:script, :node, :attr).join(' - ')
  Marty::Log.write_log('api', desc, log_hash(result, params, request))
end

.log_hash(result, params, request) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/marty/api/base.rb', line 178

def self.log_hash result, params, request
  ret_arr = params[:return_array]

  # filter sensitive information from input/output
  is_hash = result.is_a?(Hash)
  res     = is_hash ? filter_hash(result, engine_params_filter) : result
  input   = filter_hash(params[:params], engine_params_filter)
  error   = res['error'] if is_hash && res.include?('error')

  { script:     params[:script],
    node:       params[:node],
    attrs:      ret_arr ? [params[:attr]] : params[:attr],
    input:      input,
    output:     error ? nil : res,
    start_time: params[:start_time],
    end_time:   Time.zone.now,
    error:      error,
    remote_ip:  request.remote_ip,
    auth_name:  params[:auth] }
end

.process_params(params) ⇒ Object



27
28
29
# File 'lib/marty/api/base.rb', line 27

def self.process_params params
  params
end

.respond_to(controller) ⇒ Object



10
11
12
13
14
15
16
17
18
19
20
# File 'lib/marty/api/base.rb', line 10

def self.respond_to controller
  result = yield
  controller.respond_to do |format|
    format.json { controller.send_data result.to_json }
    format.csv  do
      # SEMI-HACKY: strip outer list if there's only one element.
      result = result[0] if result.is_a?(Array) && result.length == 1
      controller.send_data Marty::DataExporter.to_csv(result)
    end
  end
end