-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
API reference generated from Open API spec file #40
Changes from 45 commits
a7226bd
07fc8c4
8bfbb38
86fb375
91f8974
425ed9c
9751b43
e54222b
ccb9689
3b16475
bb62ccc
0544303
0a4f376
8b708c4
ca4947a
98c22f0
2602617
dd4a689
ce68d0c
db50d2b
c1b32b5
499c82e
786d7df
4d296cb
2e854fe
66a427a
f031dd6
e4d32f1
5f8f7e7
912c261
fbcc319
318fad3
f506593
0baf0cb
ac02d34
4ce8f0c
2cacadc
b152eae
6d9f458
b540f87
400e3eb
51e9e97
0b674d1
f94a470
31621db
b235f2a
ca0ae9b
7be0c58
7492727
d77d2a5
51115f4
59d3473
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,3 +41,5 @@ github_repo: alphagov/example-repo | |
|
||
redirects: | ||
/something/old.html: /index.html | ||
|
||
api_path: source/pets.yml |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
title: API /Pets | ||
--- | ||
|
||
# API /Pets | ||
|
||
api> /pets |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
title: Example API Petstore | ||
--- | ||
|
||
api> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
openapi: "3.0.0" | ||
info: | ||
version: 1.0.0 | ||
title: Swagger Petstore | ||
description: A sample API that uses a petstore as an example to demonstrate features in the OpenAPI 3.0 specification | ||
license: | ||
name: MIT | ||
servers: | ||
- url: http://petstore.swagger.io/v1 | ||
paths: | ||
/pets: | ||
get: | ||
summary: List all pets | ||
operationId: listPets | ||
tags: | ||
- pets | ||
parameters: | ||
- name: limit | ||
in: query | ||
description: How many items to return at one time (max 100) | ||
required: false | ||
schema: | ||
type: integer | ||
format: int32 | ||
responses: | ||
'200': | ||
description: A paged array of pets | ||
headers: | ||
content: | ||
application/json: | ||
schema: | ||
$ref: "#/components/schemas/Pets" | ||
default: | ||
description: unexpected error | ||
content: | ||
application/json: | ||
schema: | ||
$ref: "#/components/schemas/Error" | ||
post: | ||
summary: Create a pet | ||
operationId: createPets | ||
tags: | ||
- pets | ||
responses: | ||
'201': | ||
description: Null response | ||
default: | ||
description: unexpected error | ||
content: | ||
application/json: | ||
schema: | ||
$ref: "#/components/schemas/Error" | ||
/pets/{petId}: | ||
get: | ||
summary: Info for a specific pet | ||
operationId: showPetById | ||
tags: | ||
- pets | ||
parameters: | ||
- name: petId | ||
in: path | ||
required: true | ||
description: The id of the pet to retrieve | ||
schema: | ||
type: string | ||
responses: | ||
'200': | ||
description: Expected response to a valid request | ||
content: | ||
application/json: | ||
schema: | ||
$ref: "#/components/schemas/Pets" | ||
default: | ||
description: unexpected error | ||
content: | ||
application/json: | ||
schema: | ||
$ref: "#/components/schemas/Error" | ||
components: | ||
schemas: | ||
Pet: | ||
required: | ||
- id | ||
- name | ||
properties: | ||
id: | ||
type: integer | ||
format: int64 | ||
name: | ||
type: string | ||
tag: | ||
type: string | ||
Pets: | ||
type: array | ||
items: | ||
$ref: "#/components/schemas/Pet" | ||
Error: | ||
required: | ||
- code | ||
- message | ||
properties: | ||
code: | ||
type: integer | ||
format: int32 | ||
message: | ||
type: string |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
require 'erb' | ||
require 'openapi3_parser' | ||
require 'uri' | ||
require 'pry' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this pry can be removed |
||
|
||
module GovukTechDocs | ||
class ApiReference < Middleman::Extension | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think this extension would be usable by other middleman projects not using the tech-docs-gem? If so it would be great if we could package this up separately at some point, to contribute back to the wider ecosystem of openapi tools. |
||
expose_to_application api: :api | ||
|
||
def initialize(app, options_hash = {}, &block) | ||
super | ||
|
||
@app = app | ||
@config = @app.config[:tech_docs] | ||
|
||
# If no api path then just return. | ||
if @config['api_path'].to_s.empty? | ||
# @TODO Throw a middleman error? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this raise an error that tells you what you need to add? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in latest commit. |
||
return | ||
end | ||
|
||
# Is the api_path a url or path? | ||
if uri?@config['api_path'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor style thing but it looks weird that there's no space between the method call and its argument here, can you change it to use the parentheses style? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in new commit |
||
@api_parser = true | ||
@document = Openapi3Parser.load_url(@config['api_path']) | ||
elsif File.exist?(@config['api_path']) | ||
# Load api file and set existence flag. | ||
@api_parser = true | ||
@document = Openapi3Parser.load_file(@config['api_path']) | ||
else | ||
# @TODO Throw a middleman error? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. another TODO here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in latest commit |
||
@api_parser = false | ||
end | ||
|
||
# Load template files | ||
@render_api_full = get_renderer('api_reference_full.html.erb') | ||
@render_path = get_renderer('path.html.erb') | ||
@render_schema = get_renderer('schema.html.erb') | ||
end | ||
|
||
def uri?(string) | ||
uri = URI.parse(string) | ||
%w(http https).include?(uri.scheme) | ||
rescue URI::BadURIError | ||
false | ||
rescue URI::InvalidURIError | ||
false | ||
end | ||
|
||
def api(text) | ||
if @api_parser == true | ||
|
||
map = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you give this a more descriptive name? This makes the following line a bit harder to parse (map.map) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed to keywords in a new commit. |
||
'api>' => 'default', | ||
'api_schema>' => 'schema' | ||
} | ||
|
||
regexp = map.map { |k, _| Regexp.escape(k) }.join('|') | ||
|
||
md = text.match(/^<p>(#{regexp})/) | ||
if md | ||
key = md.captures[0] | ||
type = map[key] | ||
|
||
text.gsub!(/#{ Regexp.escape(key) }\s+?/, '') | ||
|
||
# Strip paragraph tags from text | ||
text = text.gsub(/<\/?[^>]*>/, '') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would also strip other tags from the text, if there are any. Is it valid for other stuff to appear in the paragraph besides the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not at the moment, it only works if that's the only thing on the line. Is there a use case where you'd want to include this in a paragraph? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so - requiring it to be in its own paragraph makes sense I think. |
||
text = text.strip | ||
|
||
if type == 'default' | ||
api_path_render(text) | ||
else | ||
api_schema_render(text) | ||
end | ||
|
||
else | ||
return text | ||
end | ||
else | ||
text | ||
end | ||
end | ||
|
||
def api_path_render(text) | ||
if text == 'api>' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this forgive trailing whitespace after the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it does from my testing. |
||
return api_full | ||
else | ||
# Call api parser on text | ||
path = @document.paths[text] | ||
output = @render_path.result(binding) | ||
return output | ||
end | ||
end | ||
|
||
def api_schema_render(text) | ||
schemas = '' | ||
schemas_data = @document.components.schemas | ||
schemas_data.each do |schema_data| | ||
if schema_data[0] == text | ||
print schema_data[0] | ||
print schema_data[1] | ||
end | ||
end | ||
return text | ||
end | ||
|
||
def api_full | ||
info = api_info | ||
server = api_server | ||
|
||
paths = '' | ||
paths_data = @document.paths | ||
paths_data.each do |path_data| | ||
# For some reason paths.each returns an array of arrays [title, object] | ||
# instead of an array of objects | ||
text = path_data[0] | ||
path = path_data[1] | ||
paths += @render_path.result(binding) | ||
end | ||
schemas = '' | ||
schemas_data = @document.components.schemas | ||
schemas_data.each do |schema_data| | ||
title = schema_data[0] | ||
schema = schema_data[1] | ||
schemas += @render_schema.result(binding) | ||
end | ||
@render_api_full.result(binding) | ||
end | ||
|
||
def render_markdown(text) | ||
if text | ||
Tilt['markdown'].new(context: @app) { text }.render | ||
end | ||
end | ||
|
||
private | ||
|
||
def get_renderer(file) | ||
template_path = File.join(File.dirname(__FILE__), 'templates/' + file) | ||
template = File.open(template_path, 'r').read | ||
ERB.new(template) | ||
end | ||
|
||
def get_operations(path) | ||
operations = {} | ||
operations['get'] = path.get if defined? path.get | ||
operations['put'] = path.put if defined? path.put | ||
operations['post'] = path.post if defined? path.post | ||
operations['delete'] = path.delete if defined? path.delete | ||
operations['patch'] = path.patch if defined? path.patch | ||
operations | ||
end | ||
|
||
def api_info | ||
@document.info | ||
end | ||
|
||
def api_server | ||
@document.servers[0] | ||
end | ||
|
||
def get_schema_name(text) | ||
unless text.is_a?(String) | ||
return nil | ||
end | ||
# Schema dictates that it's always components['schemas'] | ||
text.gsub(/#\/components\/schemas\//, '') | ||
end | ||
end | ||
end | ||
|
||
::Middleman::Extensions.register(:api_reference, GovukTechDocs::ApiReference) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<h1 id="<%= info.title.parameterize %>"><%= info.title %> v<%= info.version %></h1> | ||
<%= render_markdown(info.description) %> | ||
<% if server %> | ||
<h2 id="base-url">Base URL</h2> | ||
<p><strong><%= server.url %></strong></p> | ||
<% end %> | ||
<%= paths %> | ||
<h2 id="schemas">Schemas</h2> | ||
<%= schemas %> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should say what gets generated when you provide this.
From the example it looks like you need to create a source file like this as well as specifying the api path to get any output?