Module: Archsight::Web::API::JsonHelpers

Defined in:
lib/archsight/web/api/json_helpers.rb

Overview

Shared helpers for JSON API responses

Constant Summary collapse

DEFAULT_LIMIT =
50
MAX_LIMIT =
500
MARKDOWN_ANNOTATION_KEYS =
Set["architecture/description"].freeze

Instance Method Summary collapse

Instance Method Details

#build_analysis_result(result) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/archsight/web/api/json_helpers.rb', line 179

def build_analysis_result(result)
  data = {
    name: result.name,
    success: result.success?,
    has_findings: result.has_findings?,
    duration: result.duration,
    sections: result.sections.map { |s| serialize_analysis_section(s) }
  }

  if result.failed?
    data[:error] = result.error
    data[:error_backtrace] = result.error_backtrace if result.error_backtrace&.any?
  end

  data
end

#build_count_response(query, results, query_time_ms) ⇒ Object



121
122
123
124
125
126
127
128
129
130
# File 'lib/archsight/web/api/json_helpers.rb', line 121

def build_count_response(query, results, query_time_ms)
  by_kind = results.group_by { |r| r.class.to_s.split("::").last }
                   .transform_values(&:length)
  {
    query: query,
    total: results.length,
    query_time_ms: query_time_ms,
    by_kind: by_kind
  }
end

#build_filters_response(kind) ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
# File 'lib/archsight/web/api/json_helpers.rb', line 167

def build_filters_response(kind)
  db.filters_for_kind(kind).map do |annotation, values|
    {
      key: annotation.key,
      title: annotation.title,
      description: annotation.description,
      filter_type: annotation.filter.to_s,
      values: values
    }
  end
end

#build_instance_response(kind, instance) ⇒ Object



88
89
90
91
92
93
94
95
96
97
# File 'lib/archsight/web/api/json_helpers.rb', line 88

def build_instance_response(kind, instance)
  {
    kind: kind,
    name: instance.name,
    metadata: { annotations: render_annotations(instance.annotations) },
    spec: serialize_spec(instance.spec),
    relations: extract_relations(instance),
    references: extract_references(instance)
  }
end

#build_kinds_listObject



61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/archsight/web/api/json_helpers.rb', line 61

def build_kinds_list
  kinds = Archsight::Resources.resource_classes.map do |kind_name, klass|
    count = db.instances_by_kind(kind_name).length
    {
      kind: kind_name,
      description: klass.description,
      layer: klass.layer,
      icon: klass.icon,
      instance_count: count
    }
  end
  kinds.sort_by { |k| k[:kind] }
end

#build_list_response(kind, pagination, instances) ⇒ Object



75
76
77
78
79
80
81
82
83
84
# File 'lib/archsight/web/api/json_helpers.rb', line 75

def build_list_response(kind, pagination, instances)
  {
    kind: kind,
    total: pagination[:total],
    limit: pagination[:limit],
    offset: pagination[:offset],
    count: instances.length,
    instances: instances
  }
end

#build_search_response(query, results, _parsed_query, query_time_ms) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/archsight/web/api/json_helpers.rb', line 132

def build_search_response(query, results, _parsed_query, query_time_ms)
  limit, offset = parse_pagination_params
  output = parse_output_param
  sorted = results.sort_by(&:name)
  pagination = paginate(sorted, limit: limit, offset: offset)

  instances = pagination[:items].map do |r|
    resource_summary(r, output: output, omit_kind: false)
  end

  {
    query: query,
    total: pagination[:total],
    limit: pagination[:limit],
    offset: pagination[:offset],
    count: instances.length,
    query_time_ms: query_time_ms,
    instances: instances
  }
end

#extract_references(instance) ⇒ Object



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/archsight/web/api/json_helpers.rb', line 215

def extract_references(instance)
  references = {}

  instance.references.each do |ref|
    inst = ref[:instance]
    verb = ref[:verb]
    kind = inst.klass

    references[kind] ||= {}
    references[kind][verb] ||= []
    references[kind][verb] << inst.name
  end

  references
end

#extract_relations(instance) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/archsight/web/api/json_helpers.rb', line 153

def extract_relations(instance)
  relations = {}

  instance.class.relations.each do |verb, spec_key, kind|
    rels = instance.relations(verb, spec_key).map(&:name)
    next if rels.empty?

    relations[verb] ||= {}
    relations[verb][kind] = rels
  end

  relations
end

#json_error(message, status:, error_type: "Error", query: nil) ⇒ Object



17
18
19
20
21
22
# File 'lib/archsight/web/api/json_helpers.rb', line 17

def json_error(message, status:, error_type: "Error", query: nil)
  content_type :json
  error = { error: error_type, message: message }
  error[:query] = query if query
  halt status, JSON.pretty_generate(error)
end

#json_response(data, status: 200) ⇒ Object



12
13
14
15
# File 'lib/archsight/web/api/json_helpers.rb', line 12

def json_response(data, status: 200)
  content_type :json
  halt status, JSON.pretty_generate(data)
end

#paginate(collection, limit:, offset:) ⇒ Object



40
41
42
43
44
45
46
47
48
49
# File 'lib/archsight/web/api/json_helpers.rb', line 40

def paginate(collection, limit:, offset:)
  limit = [[limit.to_i, 1].max, MAX_LIMIT].min
  offset = [offset.to_i, 0].max
  {
    items: collection.drop(offset).take(limit),
    limit: limit,
    offset: offset,
    total: collection.length
  }
end

#parse_output_paramObject



57
58
59
# File 'lib/archsight/web/api/json_helpers.rb', line 57

def parse_output_param
  params[:output] || "complete"
end

#parse_pagination_paramsObject



51
52
53
54
55
# File 'lib/archsight/web/api/json_helpers.rb', line 51

def parse_pagination_params
  limit = (params[:limit] || DEFAULT_LIMIT).to_i
  offset = (params[:offset] || 0).to_i
  [limit, offset]
end

#render_annotations(annotations) ⇒ Object



99
100
101
102
103
104
105
106
107
# File 'lib/archsight/web/api/json_helpers.rb', line 99

def render_annotations(annotations)
  annotations.each_with_object({}) do |(key, value), result|
    result[key] = if MARKDOWN_ANNOTATION_KEYS.include?(key) && value.is_a?(String)
                    markdown(value)
                  else
                    value
                  end
  end
end

#resource_summary(resource, output:, omit_kind: false) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/archsight/web/api/json_helpers.rb', line 24

def resource_summary(resource, output:, omit_kind: false)
  result = case output
           when "brief"
             Archsight::MCP.brief_summary(resource, omit_kind: omit_kind)
           when "annotations"
             Archsight::MCP.annotations_summary(resource, omit_kind: omit_kind)
           else # "complete"
             summary = Archsight::MCP.complete_summary(resource, omit_kind: omit_kind)
             summary[:spec] = serialize_spec(summary[:spec]) if summary[:spec]
             summary
           end
  result[:icon] = resource.class.icon
  result[:layer] = resource.class.layer
  result
end

#serialize_analysis_section(section) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/archsight/web/api/json_helpers.rb', line 196

def serialize_analysis_section(section)
  case section[:type]
  when :heading
    { type: "heading", level: section[:level], text: section[:text] }
  when :text
    { type: "text", content: section[:content] }
  when :message
    { type: "message", level: section[:level].to_s, message: section[:message] }
  when :table
    { type: "table", headers: section[:headers], rows: section[:rows] }
  when :list
    { type: "list", items: section[:items] }
  when :code
    { type: "code", lang: section[:lang], content: section[:content] }
  else
    { type: section[:type].to_s }
  end
end

#serialize_spec(spec) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
# File 'lib/archsight/web/api/json_helpers.rb', line 109

def serialize_spec(spec)
  spec.transform_values do |kinds|
    next kinds unless kinds.is_a?(Hash)

    kinds.transform_values do |instances|
      next instances unless instances.is_a?(Array)

      instances.map { |i| i.is_a?(Archsight::Resources::Base) ? i.name : i }
    end
  end
end