lms-api 1.22.0 → 1.23.0
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.
- checksums.yaml +4 -4
- data/lib/canvas_api/builder.rb +59 -43
- data/lib/canvas_api/go_helpers.rb +604 -0
- data/lib/canvas_api/helpers.rb +17 -0
- data/lib/canvas_api/js_graphql_helpers.rb +8 -1
- data/lib/canvas_api/rb_graphql_helpers.rb +6 -22
- data/lib/canvas_api/render.rb +13 -2
- data/lib/canvas_api/templates/go_action.erb +241 -0
- data/lib/canvas_api/templates/go_struct.erb +44 -0
- data/lib/canvas_api/templates/rb_graphql_field.erb +2 -2
- data/lib/canvas_api/templates/rb_graphql_input_type.erb +1 -1
- data/lib/canvas_api/templates/rb_graphql_resolver.erb +1 -1
- data/lib/lms/canvas_urls.rb +61 -36
- data/lib/lms/course_ids_required.rb +12 -5
- data/lib/lms/version.rb +1 -1
- data/lib/tasks/canvas_api.rake +5 -1
- metadata +10 -6
@@ -0,0 +1,604 @@
|
|
1
|
+
module CanvasApi
|
2
|
+
module GoHelpers
|
3
|
+
def struct_fields(model, resource_name)
|
4
|
+
if !model["properties"]
|
5
|
+
puts "NO properties for #{resource_name} !!!!!!!!!!!!!!!!!!!!!"
|
6
|
+
return []
|
7
|
+
end
|
8
|
+
|
9
|
+
time_required = false
|
10
|
+
fields = model["properties"].map do |name, property|
|
11
|
+
description = ""
|
12
|
+
description << "#{safe_rb(property['description'].gsub("\n", "\n //"))}." if property["description"].present?
|
13
|
+
description << "Example: #{safe_rb(property['example'])}".gsub("..", "").gsub("\n", " ") if property["example"].present?
|
14
|
+
|
15
|
+
# clean up name
|
16
|
+
name = nested_arg(name)
|
17
|
+
|
18
|
+
if type = go_type(name, property, false, model, "")
|
19
|
+
if type.include? "time.Time"
|
20
|
+
time_required = true
|
21
|
+
end
|
22
|
+
go_declaration(name, type) + " // #{description}"
|
23
|
+
else
|
24
|
+
raise "Unable to determine type for #{name}"
|
25
|
+
end
|
26
|
+
end.compact
|
27
|
+
|
28
|
+
[fields, time_required]
|
29
|
+
end
|
30
|
+
|
31
|
+
# go_struct_fields handles the various forms of parameters documented in the Canvas API
|
32
|
+
# Examples:
|
33
|
+
#
|
34
|
+
# notification_preferences[<X>][frequency]
|
35
|
+
# Generates:
|
36
|
+
# NotificationPreferences map[string]string `json:"notification_preferences"`
|
37
|
+
#
|
38
|
+
# calendar_event[child_event_data][X][start_at]
|
39
|
+
# Generates:
|
40
|
+
# type ChildEventData struct {
|
41
|
+
# StartAt time.Time `json:"start_at"` // (Optional)
|
42
|
+
# EndAt time.Time `json:"end_at"` // (Optional)
|
43
|
+
# ContextCode string `json:"context_code"` // (Optional)
|
44
|
+
# }
|
45
|
+
# CalendarEvent struct {
|
46
|
+
# ChildEventData map[string]*ChildEventData
|
47
|
+
# }
|
48
|
+
#
|
49
|
+
# module_item[completion_requirement][type]
|
50
|
+
#
|
51
|
+
# events[start_at]
|
52
|
+
#
|
53
|
+
# Nested param
|
54
|
+
# account_notification[subject]
|
55
|
+
# Generates:
|
56
|
+
# AccountNotification struct {
|
57
|
+
# Subject string `json:"subject"` // (Required)
|
58
|
+
# Message string `json:"message"` // (Required)
|
59
|
+
# StartAt time.Time `json:"start_at"` // (Required)
|
60
|
+
# EndAt time.Time `json:"end_at"` // (Required)
|
61
|
+
# Icon string `json:"icon"` // (Optional) . Must be one of warning, information, question, error, calendar
|
62
|
+
# } `json:"account_notification"`
|
63
|
+
#
|
64
|
+
def go_struct_fields(nickname, params)
|
65
|
+
nested = {}
|
66
|
+
params.each do |p|
|
67
|
+
structs, name = split_nested(p)
|
68
|
+
go_to_tree(nickname, nested, structs, name, p)
|
69
|
+
end
|
70
|
+
go_render_params(nested)
|
71
|
+
end
|
72
|
+
|
73
|
+
def go_render_params(nested)
|
74
|
+
out = ""
|
75
|
+
nested.each do |name, val|
|
76
|
+
if val["paramType"]
|
77
|
+
out << "\n" + go_param_to_field(val, name)
|
78
|
+
elsif val[:array_of]
|
79
|
+
out << "\n#{struct_name(name)} #{val[:array_of]}"
|
80
|
+
elsif val[:map_of]
|
81
|
+
out << "\n#{struct_name(name)} #{val[:map_of]}"
|
82
|
+
else
|
83
|
+
out << "\n#{struct_name(name)} struct {"
|
84
|
+
out << go_render_params(val)
|
85
|
+
out << "\n} `json:\"#{name.underscore.gsub("`", "")}\" url:\"#{name.underscore.gsub("`", "")},omitempty\"`\n"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
out
|
89
|
+
end
|
90
|
+
|
91
|
+
def go_render_child_structs
|
92
|
+
out = ""
|
93
|
+
@child_structs&.each do |name, params|
|
94
|
+
out << "\ntype #{struct_name(name)} struct {"
|
95
|
+
params.each do |n, p|
|
96
|
+
out << "\n" + go_param_to_field(p, n)
|
97
|
+
end
|
98
|
+
out << "\n}\n"
|
99
|
+
end
|
100
|
+
out
|
101
|
+
end
|
102
|
+
|
103
|
+
# HACK for https://canvas.instructure.com/doc/api/quiz_submissions.html
|
104
|
+
# update_student_question_scores_and_comments has a param with the following form
|
105
|
+
# {"paramType"=>"form", "name"=>"quiz_submissions[questions]", "type"=>"array", "format"=>nil, "required"=>false, "deprecated"=>false, "items"=>{"$ref"=>"Hash"}}
|
106
|
+
QuizSubmissionOverrides = "QuizSubmissionOverrides"
|
107
|
+
|
108
|
+
def go_to_tree(nickname, nested, structs, name, param)
|
109
|
+
@child_structs ||= {}
|
110
|
+
|
111
|
+
# HACK for https://canvas.instructure.com/doc/api/quiz_submissions.html
|
112
|
+
if nickname == "update_student_question_scores_and_comments"
|
113
|
+
@child_structs[QuizSubmissionOverrides] = {
|
114
|
+
"score" => {"name"=>"score", "type"=>"float"},
|
115
|
+
"comment" => {"name"=>"comment", "type"=>"string"},
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
if structs.length > 0
|
120
|
+
struct, rest = structs.first, structs[1..-1]
|
121
|
+
nested[struct] ||= {}
|
122
|
+
if rest
|
123
|
+
if is_x_param?(rest[1])
|
124
|
+
type = go_property_type(name, param)
|
125
|
+
child_name = rest[0]
|
126
|
+
child_struct = "#{struct_name(nickname)}#{struct_name(child_name)}"
|
127
|
+
@child_structs[child_struct] ||= {}
|
128
|
+
@child_structs[child_struct][name] = param
|
129
|
+
nested[struct][child_name] = {
|
130
|
+
"name" => "#{struct}[#{child_name}]",
|
131
|
+
"type" => "map[string]#{child_struct}",
|
132
|
+
"paramType" => param["paramType"],
|
133
|
+
"keep_type" => true,
|
134
|
+
}
|
135
|
+
rest.shift
|
136
|
+
rest.shift
|
137
|
+
elsif is_x_param?(rest[0])
|
138
|
+
if rest[0] == structs[1]
|
139
|
+
child_name = structs[0]
|
140
|
+
child_struct = "#{struct_name(nickname)}#{struct_name(child_name)}"
|
141
|
+
nested[struct][:map_of] = "map[string]#{child_struct}"
|
142
|
+
@child_structs[child_struct] ||= {}
|
143
|
+
@child_structs[child_struct][name] = param
|
144
|
+
else
|
145
|
+
type = go_property_type(name, param)
|
146
|
+
nested[struct][:map_of] = "map[string]#{type}"
|
147
|
+
end
|
148
|
+
rest.shift
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
if rest && rest.length > 0
|
153
|
+
go_to_tree(nickname, nested[struct], rest, name, param)
|
154
|
+
else
|
155
|
+
if @child_structs && child_struct = nested[struct][:array_of]
|
156
|
+
@child_structs[child_struct][name] = param
|
157
|
+
else
|
158
|
+
nested[struct][name] = param
|
159
|
+
end
|
160
|
+
end
|
161
|
+
else
|
162
|
+
nested[name] = param
|
163
|
+
if param["type"] == "array" && ["events"].include?(param["name"])
|
164
|
+
child_struct = "#{struct_name(nickname)}#{struct_name(param["name"])}"
|
165
|
+
@child_structs ||= {}
|
166
|
+
@child_structs[child_struct] ||= {}
|
167
|
+
nested[name][:array_of] = child_struct
|
168
|
+
puts "******** Using custom struct #{child_struct}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def go_param_path(param)
|
174
|
+
if is_x_param?(param["name"])
|
175
|
+
"#{go_param_kind(param)}.#{go_name(param["name"])}"
|
176
|
+
elsif is_nested?(param)
|
177
|
+
structs, name = split_nested(param)
|
178
|
+
"#{go_param_kind(param)}.#{structs.map{|s| struct_name(s)}.join('.')}.#{go_name(name)}"
|
179
|
+
else
|
180
|
+
"#{go_param_kind(param)}.#{go_name(param["name"])}"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def is_x_param?(name)
|
185
|
+
if name
|
186
|
+
name.include?("[<X>]") ||
|
187
|
+
name.include?("<X>") ||
|
188
|
+
name.include?("X") ||
|
189
|
+
name.include?("<student_id>") ||
|
190
|
+
name.include?("0")
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def is_nested?(param)
|
195
|
+
param["name"].include?("[")
|
196
|
+
end
|
197
|
+
|
198
|
+
def is_array(param)
|
199
|
+
param["type"] == "array"
|
200
|
+
end
|
201
|
+
|
202
|
+
def split_nested(param)
|
203
|
+
parts = param["name"].split("[").map{|p| p.gsub("]", "")}
|
204
|
+
[
|
205
|
+
parts[0...-1],
|
206
|
+
parts.last,
|
207
|
+
]
|
208
|
+
end
|
209
|
+
|
210
|
+
def go_param_to_field(parameter, name = nil)
|
211
|
+
name ||= parameter["name"]
|
212
|
+
type = go_type(name, parameter)
|
213
|
+
go_declaration(name, type) + " // " + go_comments(parameter, false)
|
214
|
+
end
|
215
|
+
|
216
|
+
def go_comments(parameter, include_description = true)
|
217
|
+
out = " (#{parameter["required"] ? 'Required' : 'Optional'}) "
|
218
|
+
if parameter["enum"]
|
219
|
+
out << ". Must be one of #{parameter["enum"].join(', ')}"
|
220
|
+
end
|
221
|
+
if include_description && parameter["description"]
|
222
|
+
out << parameter["description"].gsub("\n", "\n// ")
|
223
|
+
end
|
224
|
+
out
|
225
|
+
end
|
226
|
+
|
227
|
+
def go_parameter_doc(parameter)
|
228
|
+
name = parameter["name"]
|
229
|
+
out = "# #{go_param_path(parameter)}"
|
230
|
+
out << go_comments(parameter)
|
231
|
+
out
|
232
|
+
end
|
233
|
+
|
234
|
+
def go_param_empty_value(parameter)
|
235
|
+
name = parameter["name"]
|
236
|
+
if is_x_param?(name)
|
237
|
+
return "nil"
|
238
|
+
end
|
239
|
+
type = go_type(name, parameter)
|
240
|
+
case type
|
241
|
+
when "int64"
|
242
|
+
when "int"
|
243
|
+
when "float64"
|
244
|
+
"0"
|
245
|
+
when "string"
|
246
|
+
'""'
|
247
|
+
when "time.Time"
|
248
|
+
"nil"
|
249
|
+
when "map[string](interface{})"
|
250
|
+
"nil"
|
251
|
+
else
|
252
|
+
if type.include?("[]")
|
253
|
+
"nil"
|
254
|
+
else
|
255
|
+
"need empty value for #{type}"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def is_required_field(parameter)
|
261
|
+
parameter["required"] && !["bool", "int64", "int", "float64"].include?(go_type(parameter["name"], parameter))
|
262
|
+
end
|
263
|
+
|
264
|
+
def go_declaration(name, type)
|
265
|
+
json = name.underscore.split("[")[0].gsub("`rlid`", "rlid")
|
266
|
+
out = "#{go_name(name)} #{type} `json:\"#{json}\" url:\"#{json},omitempty\"`"
|
267
|
+
end
|
268
|
+
|
269
|
+
def go_name(name)
|
270
|
+
parts = name.split("[")
|
271
|
+
parts[0].camelize.gsub("-", "").gsub("_", "")
|
272
|
+
.gsub("`rlid`", "RLID")
|
273
|
+
.gsub("Id", "ID")
|
274
|
+
.gsub("url", "URL")
|
275
|
+
.gsub("Sis", "SIS")
|
276
|
+
.gsub("MediaTrackk", "MediaTrack")
|
277
|
+
.gsub("Https:::::Canvas.instructure.com::Lti::Submission", "CanvasLTISubmission")
|
278
|
+
end
|
279
|
+
|
280
|
+
def struct_name(type)
|
281
|
+
# Remove chars and fix spelling errors
|
282
|
+
cleaned = type.split('|').first.strip.gsub(" ", "_")
|
283
|
+
go_name(cleaned)
|
284
|
+
end
|
285
|
+
|
286
|
+
def go_require_models(parameters, nickname, return_type)
|
287
|
+
parameters.any? { |p| go_type(p["name"], p).include?("models") } ||
|
288
|
+
["assign_unassigned_members"].include?(@nickname) ||
|
289
|
+
(return_type &&
|
290
|
+
return_type != "bool" &&
|
291
|
+
!return_type.include?("string") &&
|
292
|
+
!return_type.include?("SuccessResponse") &&
|
293
|
+
!return_type.include?("UnreadCount")
|
294
|
+
)
|
295
|
+
end
|
296
|
+
|
297
|
+
def time_required?(parameters)
|
298
|
+
parameters.any? { |p| go_type(p["name"], p).include?("time.Time") }
|
299
|
+
end
|
300
|
+
|
301
|
+
def go_type(name, property, return_type = false, model = nil, namespace = "models.")
|
302
|
+
if property["$ref"]
|
303
|
+
"*#{namespace}#{struct_name(property['$ref'])}"
|
304
|
+
else
|
305
|
+
go_property_type(name, property, return_type, model, namespace)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def go_property_type(name, property, return_type = false, model = nil, namespace = "models.")
|
310
|
+
return property["type"] if property["keep_type"]
|
311
|
+
return property[:array_of] if property[:array_of]
|
312
|
+
|
313
|
+
# Canvas API docs are wrong for these so we HACK in the right type
|
314
|
+
return "float64" if name.downcase == "points_possible"
|
315
|
+
|
316
|
+
type = property["type"].downcase
|
317
|
+
case type
|
318
|
+
when "{success: true}"
|
319
|
+
"canvasapi.SuccessResponse"
|
320
|
+
when "integer", "string", "boolean", "datetime", "number", "date"
|
321
|
+
go_primitive(name, type, property["format"])
|
322
|
+
when "void"
|
323
|
+
"bool" # TODO this doesn't seem right?
|
324
|
+
when "array"
|
325
|
+
go_ref_property_type(property, namespace)
|
326
|
+
when "object"
|
327
|
+
puts "Using string type for '#{name}' ('#{property}') of type object."
|
328
|
+
"map[string](interface{})"
|
329
|
+
else
|
330
|
+
if property["type"] == "list of content items"
|
331
|
+
# HACK There's no list of content items object so we return an array of string
|
332
|
+
"[]string"
|
333
|
+
elsif property["type"].include?('{ "unread_count": "integer" }')
|
334
|
+
"canvasapi.UnreadCount"
|
335
|
+
elsif return_type
|
336
|
+
"*#{namespace}#{struct_name(property["type"])}"
|
337
|
+
elsif property["type"] == "Hash"
|
338
|
+
"map[string](interface{})"
|
339
|
+
elsif property["type"] == "String[]"
|
340
|
+
"[]string"
|
341
|
+
elsif property["type"] == "[Answer]"
|
342
|
+
"[]*models.Answer"
|
343
|
+
elsif property["type"] == "QuizUserConversation"
|
344
|
+
"canvasapi.QuizUserConversation"
|
345
|
+
elsif [
|
346
|
+
"Assignment",
|
347
|
+
"BlueprintRestriction",
|
348
|
+
"RubricAssessment",
|
349
|
+
].include?(property["type"])
|
350
|
+
"*models.#{property["type"]}"
|
351
|
+
elsif property["type"] == "multiple BlueprintRestrictions"
|
352
|
+
"[]*models.BlueprintRestriction"
|
353
|
+
elsif property["type"] == "File"
|
354
|
+
# This won't work. If we ever need to use this type we'll need to do some refactoring
|
355
|
+
"string"
|
356
|
+
elsif property["type"] == "Deprecated"
|
357
|
+
"string"
|
358
|
+
elsif property["type"] == "SerializedHash"
|
359
|
+
# Not sure this will work
|
360
|
+
"map[string](interface{})"
|
361
|
+
elsif property["type"].downcase == "json"
|
362
|
+
"map[string](interface{})"
|
363
|
+
elsif ["Numeric", "float"].include?(property["type"])
|
364
|
+
"float64"
|
365
|
+
elsif property["type"] == "GroupMembership | Progress"
|
366
|
+
"no-op" # this is handled further up the stack
|
367
|
+
elsif property["type"] == "URL"
|
368
|
+
"string"
|
369
|
+
else
|
370
|
+
raise "Unable to match '#{name}' requested property '#{property}' to Go Type."
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def go_ref_property_type(property, namespace)
|
376
|
+
ref_type = property.dig("items", "$ref")
|
377
|
+
if ref_type == nil
|
378
|
+
if property["type"] == "array"
|
379
|
+
"[]string"
|
380
|
+
else
|
381
|
+
"string"
|
382
|
+
end
|
383
|
+
elsif ref_type == "Hash"
|
384
|
+
# HACK for https://canvas.instructure.com/doc/api/quiz_submissions.html
|
385
|
+
if property["name"] == "quiz_submissions[questions]"
|
386
|
+
"map[string]QuizSubmissionOverrides"
|
387
|
+
else
|
388
|
+
raise "No type available for #{property}"
|
389
|
+
end
|
390
|
+
elsif ref_type == "[Integer]"
|
391
|
+
"[]int"
|
392
|
+
elsif ref_type == "Array"
|
393
|
+
"[]string"
|
394
|
+
elsif ref_type == "[String]"
|
395
|
+
"[]string"
|
396
|
+
elsif ref_type == "DateTime" || ref_type == "Date"
|
397
|
+
"[]time.Time"
|
398
|
+
elsif ref_type == "object"
|
399
|
+
"map[string](interface{})"
|
400
|
+
elsif ref_type
|
401
|
+
# HACK on https://canvas.instructure.com/doc/api/submissions.json
|
402
|
+
# the ref value is set to a full sentence rather than a
|
403
|
+
# simple type, so we look for that specific value
|
404
|
+
if ref_type.include?("UserDisplay if anonymous grading is not enabled")
|
405
|
+
"[]*#{namespace}UserDisplay"
|
406
|
+
elsif ref_type.include?("Url String The url to the result that was created")
|
407
|
+
"string"
|
408
|
+
else
|
409
|
+
"[]*#{namespace}#{struct_name(ref_type)}"
|
410
|
+
end
|
411
|
+
else
|
412
|
+
"[]#{go_primitive(name, property["items"]["type"].downcase, property["items"]["format"])}"
|
413
|
+
end
|
414
|
+
rescue
|
415
|
+
raise "Unable to discover Go list type for '#{name}' ('#{property}')."
|
416
|
+
end
|
417
|
+
|
418
|
+
def go_primitive(name, type, format)
|
419
|
+
case type
|
420
|
+
when "integer"
|
421
|
+
if name.end_with?("_ids")
|
422
|
+
"[]int64"
|
423
|
+
else
|
424
|
+
"int64"
|
425
|
+
end
|
426
|
+
when "number"
|
427
|
+
if format == "Float"
|
428
|
+
"float64"
|
429
|
+
else
|
430
|
+
# TODO many of the LMS types with 'number' don't indicate a type so we have to guess
|
431
|
+
# Hopefully that changes. For now we go with float
|
432
|
+
"float64"
|
433
|
+
end
|
434
|
+
when "string"
|
435
|
+
"string"
|
436
|
+
when "boolean"
|
437
|
+
"bool"
|
438
|
+
when "datetime"
|
439
|
+
"time.Time"
|
440
|
+
when "date"
|
441
|
+
"time.Time"
|
442
|
+
else
|
443
|
+
raise "Unable to match requested primitive '#{type}' to Go Type."
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
def go_field_validation(model)
|
448
|
+
return nil unless model["properties"]
|
449
|
+
allowable = {}
|
450
|
+
model["properties"].each do |name, property|
|
451
|
+
if property["allowableValues"]
|
452
|
+
values = property["allowableValues"]["values"].map do |value|
|
453
|
+
"\"#{value}\""
|
454
|
+
end
|
455
|
+
allowable[name] = {
|
456
|
+
values: values,
|
457
|
+
type: property["type"],
|
458
|
+
}
|
459
|
+
end
|
460
|
+
end
|
461
|
+
allowable
|
462
|
+
end
|
463
|
+
|
464
|
+
def go_param_kind(parmeter)
|
465
|
+
case parmeter["paramType"]
|
466
|
+
when "path"
|
467
|
+
"Path"
|
468
|
+
when "query"
|
469
|
+
"Query"
|
470
|
+
when "form"
|
471
|
+
"Form"
|
472
|
+
else
|
473
|
+
"Unknown paramType"
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
def go_path_params(params)
|
478
|
+
select_params("path", params)
|
479
|
+
end
|
480
|
+
|
481
|
+
def go_query_params(params)
|
482
|
+
select_params("query", params)
|
483
|
+
end
|
484
|
+
|
485
|
+
def go_form_params(params)
|
486
|
+
select_params("form", params)
|
487
|
+
end
|
488
|
+
|
489
|
+
def select_params(type, parameters)
|
490
|
+
params = parameters.select{|p| p["paramType"] == type}
|
491
|
+
if params && !params.nil? && params.length > 0
|
492
|
+
params
|
493
|
+
else
|
494
|
+
nil
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
def go_api_url
|
499
|
+
url = @api_url
|
500
|
+
@args.each do |arg|
|
501
|
+
url.gsub(arg, "+\"#{go_name(arg)}\"+")
|
502
|
+
end
|
503
|
+
url
|
504
|
+
end
|
505
|
+
|
506
|
+
def is_paged?(operation)
|
507
|
+
operation["type"] == "array"
|
508
|
+
end
|
509
|
+
|
510
|
+
def next_param(operation)
|
511
|
+
if is_paged?(operation)
|
512
|
+
", next *url.URL"
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
def go_do_return_statement(operation, nickname)
|
517
|
+
if nickname == "assign_unassigned_members" || is_paged?(operation)
|
518
|
+
"return nil, nil, err"
|
519
|
+
elsif type = go_return_type(operation)
|
520
|
+
if type == "bool"
|
521
|
+
"return false, err"
|
522
|
+
elsif type == "string"
|
523
|
+
'return "", err'
|
524
|
+
elsif type == "integer"
|
525
|
+
"return 0, err"
|
526
|
+
else
|
527
|
+
"return nil, err"
|
528
|
+
end
|
529
|
+
else
|
530
|
+
"return err"
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
def go_do_final_return_statement(operation, nickname)
|
535
|
+
if nickname == "assign_unassigned_members"
|
536
|
+
"return &groupMembership, &progress, nil"
|
537
|
+
elsif go_return_type(operation)
|
538
|
+
if is_paged?(operation)
|
539
|
+
"return ret, pagedResource, nil"
|
540
|
+
elsif operation["type"] == "boolean" || operation["type"] == "string" || operation["type"] == "integer"
|
541
|
+
"return ret, nil"
|
542
|
+
else
|
543
|
+
"return &ret, nil"
|
544
|
+
end
|
545
|
+
else
|
546
|
+
"return nil"
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
def go_do_return_value(operation, nickname)
|
551
|
+
if nickname == "assign_unassigned_members"
|
552
|
+
# HACK. harded coded because Assign unassigned members returns different values based on input
|
553
|
+
# see https://canvas.instructure.com/doc/api/group_categories.html#method.group_categories.assign_unassigned_members
|
554
|
+
"(*models.GroupMembership, *models.Progress, error)"
|
555
|
+
elsif type = go_return_type(operation)
|
556
|
+
if is_paged?(operation)
|
557
|
+
"(#{type}, *canvasapi.PagedResource, error)"
|
558
|
+
else
|
559
|
+
"(#{type}, error)"
|
560
|
+
end
|
561
|
+
else
|
562
|
+
"error"
|
563
|
+
end
|
564
|
+
end
|
565
|
+
|
566
|
+
def go_return_type(operation, is_decl = false)
|
567
|
+
prefix = is_decl ? "" : "*"
|
568
|
+
suffix = is_decl ? "{}" : ""
|
569
|
+
if operation["type"] == "void"
|
570
|
+
nil
|
571
|
+
elsif is_paged?(operation)
|
572
|
+
model = operation.dig("items", "$ref")
|
573
|
+
if model.include?(" ")
|
574
|
+
# Handle cases with spaces using go_property_type
|
575
|
+
type = go_property_type(operation["nickname"], operation)
|
576
|
+
if type == "string"
|
577
|
+
type = "[]#{type}"
|
578
|
+
end
|
579
|
+
"#{type}#{suffix}"
|
580
|
+
else
|
581
|
+
"[]*models.#{go_name(model)}#{suffix}"
|
582
|
+
end
|
583
|
+
elsif operation["type"] == "boolean"
|
584
|
+
"bool"
|
585
|
+
elsif operation["type"] == "integer"
|
586
|
+
"int64"
|
587
|
+
elsif model = operation["type"]
|
588
|
+
if model.include?(" ")
|
589
|
+
# Handle cases with spaces using go_property_type
|
590
|
+
type = go_property_type(operation["nickname"], operation)
|
591
|
+
if type == "string"
|
592
|
+
type
|
593
|
+
else
|
594
|
+
"#{prefix}#{type}#{suffix}"
|
595
|
+
end
|
596
|
+
else
|
597
|
+
"#{prefix}models.#{go_name(model)}#{suffix}"
|
598
|
+
end
|
599
|
+
else
|
600
|
+
raise "No return type found for #{operation}"
|
601
|
+
end
|
602
|
+
end
|
603
|
+
end
|
604
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CanvasApi
|
2
|
+
def nested_arg(str)
|
3
|
+
# TODO/HACK we are replacing values from the string here to get things to work for now.
|
4
|
+
# However, removing these symbols means that the methods that use the arguments
|
5
|
+
# generated herein will have bugs and be unusable.
|
6
|
+
str.gsub("[", "_").
|
7
|
+
gsub("]", "").
|
8
|
+
gsub("*", "star").
|
9
|
+
gsub("<", "_").
|
10
|
+
gsub(">", "_").
|
11
|
+
gsub("`", "").
|
12
|
+
gsub("https://canvas.instructure.com/lti/", "").
|
13
|
+
gsub("https://www.instructure.com/", "").
|
14
|
+
gsub("https://purl.imsglobal.org/spec/lti/claim/", "").
|
15
|
+
gsub(".", "")
|
16
|
+
end
|
17
|
+
end
|
@@ -93,6 +93,13 @@ module CanvasApi
|
|
93
93
|
return str unless str.is_a?(String)
|
94
94
|
str.gsub('"', "'")
|
95
95
|
end
|
96
|
+
|
97
|
+
def graphql_resolver_class(name)
|
98
|
+
# HACK Some resolvers have both singular and plural versions, so keep the plural on those
|
99
|
+
return name.camelize if name == "get_custom_colors"
|
100
|
+
|
101
|
+
name.classify
|
102
|
+
end
|
96
103
|
end
|
97
104
|
|
98
|
-
end
|
105
|
+
end
|
@@ -9,7 +9,7 @@ module CanvasApi
|
|
9
9
|
if property["$ref"]
|
10
10
|
canvas_name(property['$ref'], input_type)
|
11
11
|
elsif property["allowableValues"]
|
12
|
-
enum_class_name(model, name)
|
12
|
+
enum_class_name(model, name, input_type)
|
13
13
|
else
|
14
14
|
type = property["type"].downcase
|
15
15
|
case type
|
@@ -106,11 +106,11 @@ module CanvasApi
|
|
106
106
|
end
|
107
107
|
end
|
108
108
|
|
109
|
-
def enum_class_name(model, field_name)
|
110
|
-
"#{model['id'].classify}#{field_name.classify}Enum"
|
109
|
+
def enum_class_name(model, field_name, input_type)
|
110
|
+
"#{model['id'].classify}#{input_type ? 'Input' : ''}#{field_name.classify}Enum"
|
111
111
|
end
|
112
112
|
|
113
|
-
def graphql_field_enums(model)
|
113
|
+
def graphql_field_enums(model, input_type = false)
|
114
114
|
return unless model["properties"]
|
115
115
|
enums = model["properties"].map do |name, property|
|
116
116
|
if property["allowableValues"]
|
@@ -118,7 +118,7 @@ module CanvasApi
|
|
118
118
|
"value \"#{value}\""
|
119
119
|
end.join("\n ")
|
120
120
|
<<-CODE
|
121
|
-
class #{enum_class_name(model, name)} < ::GraphQL::Schema::Enum
|
121
|
+
class #{enum_class_name(model, name, input_type)} < ::GraphQL::Schema::Enum
|
122
122
|
#{values}
|
123
123
|
end
|
124
124
|
CODE
|
@@ -200,7 +200,7 @@ field :#{name.underscore}, #{type}, "#{description}", resolver_method: :resolve_
|
|
200
200
|
end
|
201
201
|
|
202
202
|
def make_file_name(str)
|
203
|
-
str.underscore.split("/").last.split("|").first.gsub(
|
203
|
+
str.underscore.split("/").last.split("|").first.gsub(/^canvas_?/, "").gsub(" ", "_").strip.singularize
|
204
204
|
end
|
205
205
|
|
206
206
|
def require_from_operation(operation)
|
@@ -232,22 +232,6 @@ field :#{name.underscore}, #{type}, "#{description}", resolver_method: :resolve_
|
|
232
232
|
str.gsub('"', "'")
|
233
233
|
end
|
234
234
|
|
235
|
-
def nested_arg(str)
|
236
|
-
# TODO/HACK we are replacing values from the string here to get things to work for now.
|
237
|
-
# However, removing these symbols means that the methods that use the arguments
|
238
|
-
# generated herein will have bugs and be unusable.
|
239
|
-
str.gsub("[", "_").
|
240
|
-
gsub("]", "").
|
241
|
-
gsub("*", "star").
|
242
|
-
gsub("<", "_").
|
243
|
-
gsub(">", "_").
|
244
|
-
gsub("`", "").
|
245
|
-
gsub("https://canvas.instructure.com/lti/", "").
|
246
|
-
gsub("https://www.instructure.com/", "").
|
247
|
-
gsub("https://purl.imsglobal.org/spec/lti/claim/", "").
|
248
|
-
gsub(".", "")
|
249
|
-
end
|
250
|
-
|
251
235
|
def params_as_string(parameters, paramTypes)
|
252
236
|
filtered = parameters.select{ |p| paramTypes.include?(p["paramType"]) }
|
253
237
|
if filtered && !filtered.empty?
|