require 'set'
require 'yaml'
def clear_parameters(spec, path, method, parameters)
found = Set.new
parameters = Set.new(parameters)
spec['paths'][path][method]['parameters'].each do |parameter|
if parameters.include?(parameter['name'])
found.add(parameter['name'])
parameters.delete(parameter['name'])
elsif parameters.include?([parameter['name'], parameter['in']])
found.add([parameter['name'], parameter['in']])
parameters.delete([parameter['name'], parameter['in']])
end
end
raise "Parameter#{parameters.length > 1 ? 's' : ''} not found: #{parameters.to_a.sort.map(&:inspect).join(', ')}" unless parameters.empty?
spec['paths'][path][method]['parameters'].reject! do |parameter|
found.include?(parameter['name']) || found.include?([parameter['name'], parameter['in']])
end
end
def process(spec)
spec['paths'].each do |path, methods|
methods.each do |method, details|
if details['parameters']
details['parameters'].each do |parameter|
if parameter['in'] == 'query' && parameter['required'] == false
parameter.delete('required')
end
end
details['parameters'].reject! { |p| p['in'] == 'path' && !path.include?("{#{p['name']}}") }
path_parameters = details['parameters'].select { |p| p['in'] == 'path' }.map { |p| p['name'] }
details['parameters'].reject! { |p| p['in'] == 'query' && path_parameters.include?(p['name']) }
end
end
end
clear_parameters(spec, '/api2/categories/', 'get', %w[id long_name short_name url])
clear_parameters(spec, '/api2/categories/{bare_id}/', 'get', %w[id long_name short_name url])
clear_parameters(spec, '/api2/user-profiles/', 'get', %w[ask_when_reaffirm_question_modal date_joined default_community_visibility default_mp_visibility email first_name formerly_known_as id is_staff is_superuser last_name last_visited level levelTitle permissions purchasable_track_record score show_profile_comments supporter_level supporter_since url username can_change_username])
clear_parameters(spec, '/api2/user-profiles/{id}/', 'get', %w[ask_when_reaffirm_question_modal date_joined default_community_visibility default_mp_visibility email first_name formerly_known_as is_staff is_superuser last_name last_visited level levelTitle permissions purchasable_track_record score show_profile_comments supporter_level supporter_since url username can_change_username])
clear_parameters(spec, '/api2/user-profiles/{id}/', 'put', %w[ask_when_reaffirm_question_modal date_joined default_community_visibility default_mp_visibility email first_name formerly_known_as is_staff is_superuser last_name last_visited level levelTitle permissions purchasable_track_record score show_profile_comments supporter_level supporter_since url username can_change_username])
clear_parameters(spec, '/api2/user-profiles/{id}/', 'patch', %w[ask_when_reaffirm_question_modal date_joined default_community_visibility default_mp_visibility email first_name formerly_known_as is_staff is_superuser last_name last_visited level levelTitle permissions purchasable_track_record score show_profile_comments supporter_level supporter_since url username can_change_username])
clear_parameters(spec, '/api2/users/', 'get', %w[ask_when_reaffirm_question_modal date_joined default_community_visibility default_mp_visibility email first_name formerly_known_as id is_staff is_superuser last_name last_visited level levelTitle permissions purchasable_track_record score show_profile_comments supporter_level supporter_since url username can_change_username])
clear_parameters(spec, '/api2/users/{id}/', 'get', %w[ask_when_reaffirm_question_modal date_joined default_community_visibility default_mp_visibility email first_name formerly_known_as is_staff is_superuser last_name last_visited level levelTitle permissions purchasable_track_record score show_profile_comments supporter_level supporter_since url username can_change_username])
clear_parameters(spec, '/api2/users/{id}/', 'put', %w[ask_when_reaffirm_question_modal date_joined default_community_visibility default_mp_visibility email first_name formerly_known_as is_staff is_superuser last_name last_visited level levelTitle permissions purchasable_track_record score show_profile_comments supporter_level supporter_since url username can_change_username])
clear_parameters(spec, '/api2/users/{id}/', 'patch', %w[ask_when_reaffirm_question_modal date_joined default_community_visibility default_mp_visibility email first_name formerly_known_as is_staff is_superuser last_name last_visited level levelTitle permissions purchasable_track_record score show_profile_comments supporter_level supporter_since url username can_change_username])
clear_parameters(spec, '/api2/users/global-cp-reminder/', 'get', %w[ask_when_reaffirm_question_modal date_joined default_community_visibility default_mp_visibility email first_name formerly_known_as id is_staff is_superuser last_name last_visited level levelTitle permissions purchasable_track_record score show_profile_comments supporter_level supporter_since url username can_change_username])
clear_parameters(spec, '/api2/users/global-cp-reminder/', 'post', %w[ask_when_reaffirm_question_modal date_joined default_community_visibility default_mp_visibility email first_name formerly_known_as id is_staff is_superuser last_name last_visited level levelTitle permissions purchasable_track_record score show_profile_comments supporter_level supporter_since url username can_change_username])
schemas_were_sorted = spec['components']['schemas'].keys == spec['components']['schemas'].keys.sort
if spec['paths']['/api2/about-numbers/']['get']['responses']['200'] == { 'description' => 'No response body' }
spec['paths']['/api2/about-numbers/']['get']['responses']['200'] = {
'content' => {
'application/json' => {
'schema' => {
'$ref' => '#/components/schemas/AboutNumbers'
}
}
},
'description' => ''
}
spec['components']['schemas']['AboutNumbers'] = {
'type' => 'object',
'properties' => {
'predictions' => { 'type' => 'integer' },
'questions' => { 'type' => 'integer' },
'resolved_questions' => { 'type' => 'integer' },
'years_of_predictions' => { 'type' => 'integer' }
},
'required' => ['predictions', 'questions', 'resolved_questions', 'years_of_predictions']
}
check_reminder_schema = false
reminder_schema_lists = [
spec['paths']['/api2/users/global-cp-reminder/']['get']['responses']['200']['content'],
spec['paths']['/api2/users/global-cp-reminder/']['post']['requestBody']['content'],
spec['paths']['/api2/users/global-cp-reminder/']['post']['responses']['200']['content']
]
reminder_schema_lists.each do |list|
list.each do |content_type, content|
if content['schema']['$ref'] == '#/components/schemas/User'
content['schema']['$ref'] = '#/components/schemas/GlobalCPReminder'
check_reminder_schema = true
end
end
end
if check_reminder_schema
spec['components']['schemas']['GlobalCPReminder'] ||= {
'type' => 'object',
'properties' => {
'enabled' => { 'type' => 'boolean' },
'delta' => { 'type' => 'integer' }
},
'required' => ['enabled', 'delta']
}
end
if spec['paths']['/api2/questions/{id}/prediction-history/']['get']['responses']['200'] == { 'description' => 'No response body' }
spec['paths']['/api2/questions/{id}/prediction-history/']['get']['responses']['200'] = {
'content' => {
'application/json' => {
'schema' => {
'$ref' => '#/components/schemas/PredictionHistory'
}
}
},
'description' => ''
}
spec['components']['schemas']['PredictionHistory'] = {
'type' => 'object',
'properties' => {
'question' => { 'type' => 'integer' },
'community_prediction' => { 'type' => 'array', 'items' => { '$ref' => '#/components/schemas/PredictionHistoryTime' } },
'metaculus_prediction' => { 'type' => 'array', 'items' => { '$ref' => '#/components/schemas/PredictionHistoryTime' } },
},
'required' => ['question', 'community_prediction', 'metaculus_prediction']
}
spec['components']['schemas']['PredictionHistoryTime'] = {
'type' => 'object',
'properties' => {
't' => { 'type' => 'number', 'format' => 'double' },
'y' => { 'type' => 'array', 'items' => { 'type' => 'number', 'format' => 'double' } },
'q1' => { 'type' => 'number', 'format' => 'double' },
'q2' => { 'type' => 'number', 'format' => 'double' },
'q3' => { 'type' => 'number', 'format' => 'double' },
'low' => { 'type' => 'number', 'format' => 'double' },
'high' => { 'type' => 'number', 'format' => 'double' },
},
'required' => ['t', 'y', 'q1', 'q2', 'q3', 'low', 'high']
}
end
if spec['paths']['/api2/questions/{id}/predictions/']['get']['responses']['200'] == { 'description' => 'No response body' }
spec['paths']['/api2/questions/{id}/predictions/']['get']['responses']['200'] = {
'content' => {
'application/json' => {
'schema' => {
'type' => 'array',
'items' => {
'$ref' => '#/components/schemas/ExtendedPredictionUsername'
}
}
}
},
'description' => ''
}
end
check_predictions_schemas = ['ExtendedPredictionUsername', 'Prediction', 'PredictionUsername']
check_predictions_schemas.each do |schema|
if spec['components']['schemas'][schema]['properties']['predictions']['type'] == 'object'
spec['components']['schemas'][schema]['properties']['predictions'] = {
'type' => 'array',
'items' => {
'type' => 'object',
'additionalProperties' => {}
}
}
else
raise "#{schema} predictions already fixed"
end
end
check_effected_close_time_schemas = ['QuestionUser', 'QuestionUserDetail', 'SubQuestionUserDetail', 'SubQuestionUserList']
check_effected_close_time_schemas.each do |schema|
if spec['components']['schemas'][schema]['properties']['effected_close_time']['nullable']
raise "#{schema} effected_close_time already nullable"
end
spec['components']['schemas'][schema]['properties']['effected_close_time']['nullable'] = true
end
remove_required = {
'QuestionUser' => [
'comment_count_snapshot',
'metaculus_prediction',
'user_community_vis',
]
}
remove_required.each do |schema, properties|
properties.each do |property|
spec['components']['schemas'][schema]['required'].delete(property)
end
end
if schemas_were_sorted
spec['components']['schemas'] = spec['components']['schemas'].sort.to_h
end
end
spec['paths'].each do |path, methods|
methods.each do |method, details|
details.delete('parameters') if details['parameters'] == []
end
end
better_enum_names = {
'UserCommunityVisEnum' => {
0 => 'Depends',
-1 => 'False',
1 => 'True'
},
'ValueEnum' => {
0 => 'Neutral',
-1 => 'Down',
1 => 'Up'
},
'NotificationTypeEnum' => {
'Q' => 'QuestionResolved',
'M' => 'Mentioned',
'S' => 'ClosingSoon'
},
'QuestionUpdateStatusEnum' => {
'V' => 'Private',
'T' => 'Draft',
'I' => 'Pending'
},
'Status3baEnum' => {
'V' => 'Private',
'T' => 'Draft',
'I' => 'Pending',
'A' => 'Active',
'R' => 'Rejected',
'D' => 'Deleted'
}
}
better_enum_names.each do |schema, enum_names|
if spec['components']['schemas'][schema]['x-enum-varnames']
raise "#{schema} enum names already set"
end
if spec['components']['schemas'][schema]['enum'].to_set != enum_names.keys.to_set
raise "#{schema} enum values do not match"
end
spec['components']['schemas'][schema]['x-enum-varnames'] = enum_names.values
spec['components']['schemas'][schema]['enum'] = enum_names.keys
end
spec['components']['schemas']['UserCommunityVisEnum']['x-enum-varnames']
spec['servers'] = [{ 'url' => 'https://www.metaculus.com' }]
spec
end
spec = open('Metaculus API (1.0).yaml', 'r') { |f| YAML.load(f) }
open('Metaculus API (1.0) Modified.yaml', 'w') { |f| f.write(YAML.dump(process(spec))) }