rails_api_explorer 0.0.1.pre.1 → 0.0.2.pre.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 072d32c87b27d505bb62ce22a1d491668db4e48d
4
- data.tar.gz: ae39b35ef35b5ddf691b3a3dfcd3fcfa8e5f1841
3
+ metadata.gz: 92e109fe3ecbff439ba1527c91138e8aea97cae1
4
+ data.tar.gz: 6ae0e546fab6cb707335daaefe25c6b496658eaa
5
5
  SHA512:
6
- metadata.gz: f447e761837f2647ab0bd4dc022d6f044185ffed149547581c5e14973878cf56c2637943cf75fddda2375034fd97e2dd298995be235ea2b77235661d94bfc079
7
- data.tar.gz: 976d4f7a37d9fe5d215a0ed0896cae9f6e58f038015dda0f7b22e7be61da777b36aeca76903b07aa6a9cc0058a9b4abe9716dab7ba0a8fc7acdd6271d07094a6
6
+ metadata.gz: 5941e240df85be5594187045bed33bda332cda60ca1d5115a118eaa09b1ff2724a74be737c22c544fa93b67ab3902abc7c0aee04783c4490cd5e0b2bc31bcde5
7
+ data.tar.gz: 6a201c1994bd8100f41e6f700e669003c2b2ceae847ccd4c1974f719292f56e8c08179571cb44d26b104900f2a5aea17a831bfc8040297d391c78f1edd11649b
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # rails_api_explorer
2
+
3
+ Provides a simple DSL to describe your API, and let's you mount an interactive sandbox to explore and test it.
4
+
5
+ [Here's a demo.](http://rails-api-explorer.herokuapp.com)
6
+
7
+ This project is in the early stages of development and missing some features. Pull requests are more than welcome!
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'rails_api_explorer'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ ## Usage
22
+
23
+ Describe your API in `config/initializers/api_explorer.rb`:
24
+
25
+ ```ruby
26
+ ApiExplorer.describe do
27
+ base_url 'http://localhost:3000/api/'
28
+
29
+ shared do
30
+ header 'X-AUTH-TOKEN', source: { request: "POST:users/sign_in", accessor: "['auth_token']"}
31
+ # The source option makes the value get set automatically when a request to the given url succeeds.
32
+ # The accessor is evaluated on the JSON object returned by the server.
33
+ end
34
+
35
+ post 'users/sign_in' do
36
+ desc "Authenticate a user, returns the auth token. Anything except blank values will work for this example."
37
+
38
+ exclude_shared_header 'X-AUTH-TOKEN'
39
+
40
+ struct 'user_login' do
41
+ string 'email'
42
+ string 'password'
43
+ end
44
+ end
45
+
46
+ group "Posts" do
47
+ get 'posts'
48
+
49
+ get 'posts/:id' do
50
+ desc "Returns details for a single post."
51
+ end
52
+
53
+ post 'posts' do
54
+ desc "Create a post."
55
+ struct 'post' do
56
+ string 'title', desc: "Required."
57
+ string 'body'
58
+ end
59
+ end
60
+
61
+ patch 'posts/:id' do
62
+ struct 'post' do
63
+ string 'title', desc: "Required."
64
+ string 'body'
65
+ end
66
+ end
67
+ end
68
+ end
69
+ ```
70
+
71
+ Then mount it in your `routes.rb`:
72
+
73
+ ```ruby
74
+ mount ApiExplorer::Engine => '/api/explore', as: 'api_explorer'
75
+ ```
76
+
77
+
78
+ If you don't want the public to access the explorer, you can provide a lambda that will be executed in the `before_filter` of the controller:
79
+
80
+ ```ruby
81
+ ApiExplorer.auth = lambda do
82
+ authenticate_user!
83
+ current_user.admin? or redirect_to main_app.root_path
84
+ end
85
+ ```
86
+
87
+ The explorer is rendered within your application layout and uses bootstrap 3 classes, so it's prettier if you have that included.
88
+ Bootstrap is optional, but it requires jQuery.
89
+
90
+ ## Contributing
91
+
92
+ 1. Fork it
93
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
94
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
95
+ 4. Push to the branch (`git push origin my-new-feature`)
96
+ 5. Create a new Pull Request
@@ -12,4 +12,5 @@
12
12
  //
13
13
  //= require jquery
14
14
  //= require jquery_ujs
15
+ //= require api_explorer/serialize_object
15
16
  //= require_tree .
@@ -1,146 +1,89 @@
1
- $(function() {
2
- $(".hidden").hide().removeClass("hidden");
1
+ // Generated by CoffeeScript 1.4.0
2
+ (function() {
3
3
 
4
+ $(function() {
5
+ var setValuesFromRequest, updateShareds;
6
+ $(".hidden").hide().removeClass("hidden");
4
7
  window.responses = {};
5
-
6
8
  $(".shared-input.header").change(function() {
7
- updateShareds();
8
- });
9
- $(".shared-link").click(function() {
10
- $($(this).attr("href")).find("input").focus();
9
+ return updateShareds();
11
10
  });
12
-
13
- function updateShareds() {
14
- $(".shared-input.header").each(function() {
15
- name = $(this).attr("data-name");
16
- $(".shared.header[data-name=" + name + "]").val($(this).val());
11
+ updateShareds = function() {
12
+ return $(".shared-input.header").each(function() {
13
+ var name;
14
+ name = $(this).attr("data-name");
15
+ return $(".shared.header[data-name=" + name + "]").val($(this).val());
16
+ });
17
+ };
18
+ setValuesFromRequest = function(request) {
19
+ $("[data-source-request='" + request + "']").each(function(input) {
20
+ var v;
21
+ v = eval("responses['" + request + "']" + $(this).attr("data-source-accessor"));
22
+ return $(this).val(v);
23
+ });
24
+ return updateShareds();
25
+ };
26
+ $(".param input").prop("disabled", false);
27
+ $(".send-toggle").click(function(event) {
28
+ var param, send, setState;
29
+ event.preventDefault();
30
+ setState = function(elements, state) {
31
+ return elements.each(function(i, el) {
32
+ $(el).attr("data-send", state ? "true" : "false");
33
+ $(el).find("input").first().prop("disabled", !state);
34
+ $(el).find(".send-toggle").first().text(state ? "don't send" : "send");
35
+ return $(el).find(".name").first().toggleClass("strikethrough", !state);
17
36
  });
18
- }
19
-
20
- function setValuesFromRequest(request) {
21
- $("[data-source-request='" + request + "']").each(function(input) {
22
- v = eval("responses['" + request + "']" + $(this).attr("data-source-accessor"));
23
- $(this).val(v);
37
+ };
38
+ param = $(this).closest(".param");
39
+ send = param.attr("data-send") !== "true";
40
+ setState(param.add(param.find(".param")), send);
41
+ if (send) {
42
+ return setState(param.parents(".param"), true);
43
+ }
44
+ });
45
+ return $("form").submit(function(event) {
46
+ var form, path, req;
47
+ event.preventDefault();
48
+ form = $(this);
49
+ req = form.serializeObject().request;
50
+ form.closest(".request").find(".status").html("Requesting...");
51
+ path = req.path;
52
+ if (req.url_params) {
53
+ $.each(req.url_params, function(p, v) {
54
+ return path = path.replace(":" + p, v);
24
55
  });
25
- updateShareds();
26
- }
27
-
28
- // Enable / disable params
29
- $(".param input").prop("disabled", false); // Firefox autocomplete workaround
30
- $(".send-toggle").click(function(event) {
31
- event.preventDefault();
32
-
33
- var setState = function(elements, state) {
34
- elements.each(function(i, el) {
35
- $(el).attr("data-send", state ? "true" : "false");
36
- $(el).find("input").first().prop("disabled", !state);
37
- $(el).find(".send-toggle").first().text(state ? "don't send" : "send");
38
- $(el).find(".name").first().toggleClass("strikethrough", !state);
39
- });
56
+ }
57
+ return $.ajax({
58
+ url: path,
59
+ type: req.method,
60
+ headers: req.headers,
61
+ data: req.params,
62
+ dataType: 'json'
63
+ }).always(function(data, status, error) {
64
+ var code, key;
65
+ if (status === 'success') {
66
+ code = 200;
67
+ key = req.method.toUpperCase() + ":" + req.path.replace(api_explorer_base_url, "");
68
+ window.responses[key] = data;
69
+ setValuesFromRequest(key);
70
+ } else {
71
+ code = data.status;
72
+ try {
73
+ data = $.parseJSON(data.responseText);
74
+ } catch (SyntaxError) {
75
+
76
+ }
40
77
  }
41
-
42
- var param = $(this).closest(".param");
43
- var send = param.attr("data-send") == "true" ? false : true;
44
- setState(param.add(param.find(".param")), send); // enable or disable children
45
- if (send)
46
- setState(param.parents(".param"), true); // enable parents
47
- });
48
-
49
- $("form").submit(function(event) {
50
- event.preventDefault();
51
-
52
- var form = $(this);
53
- var req = form.serializeObject().request;
54
- form.closest(".request").find(".status").html("Requesting...");
55
-
56
- path = req.path;
57
- if (req.url_params) {
58
- $.each(req.url_params, function(p, v) {
59
- path = path.replace(":" + p, v);
60
- });
78
+ if (code === 0) {
79
+ form.closest(".request").find(".status").text("Can't reach server");
80
+ return form.closest(".request").find(".response").hide();
81
+ } else {
82
+ form.closest(".request").find(".status").text(code);
83
+ return form.closest(".request").find(".response").text(JSON.stringify(data, null, 4)).show();
61
84
  }
62
-
63
- $.ajax({
64
- url: path,
65
- type: req.method,
66
- headers: req.headers,
67
- data: req.params,
68
- dataType: 'json'
69
- }).always(function(data, status, error) {
70
- if(status == 'success') {
71
- code = 200;
72
- var key = req.method.toUpperCase() + ":" + req.path.replace(api_explorer_base_url, "");
73
- window.responses[key] = data;
74
- setValuesFromRequest(key);
75
- } else {
76
- code = data.status;
77
- try {
78
- data = $.parseJSON(data.responseText);
79
- } catch(SyntaxError) {}
80
- }
81
- if (code == 0) {
82
- form.closest(".request").find(".status").text("Can't reach server");
83
- form.closest(".request").find(".response").hide();
84
- } else {
85
- form.closest(".request").find(".status").text(code);
86
- form.closest(".request").find(".response").text(JSON.stringify(data, null, 4)).show();
87
- }
88
- });
85
+ });
89
86
  });
87
+ });
90
88
 
91
-
92
- $.fn.serializeObject = function() {
93
- var self = this,
94
- json = {},
95
- push_counters = {},
96
- patterns = {
97
- "validate": /^[a-zA-Z][a-zA-Z0-9_\-]*(?:\[(?:\d*|[a-zA-Z0-9_\-]+)\])*$/,
98
- "key": /[a-zA-Z0-9_\-]+|(?=\[\])/g,
99
- "push": /^$/,
100
- "fixed": /^\d+$/,
101
- "named": /^[a-zA-Z0-9_\-]+$/
102
- };
103
-
104
- this.build = function(base, key, value){
105
- base[key] = value;
106
- return base;
107
- };
108
- this.push_counter = function(key){
109
- if(push_counters[key] === undefined){
110
- push_counters[key] = 0;
111
- }
112
- return push_counters[key]++;
113
- };
114
- $.each($(this).serializeArray(), function(){
115
- // skip invalid keys
116
- if(!patterns.validate.test(this.name)) {
117
- return;
118
- }
119
- var k,
120
- keys = this.name.match(patterns.key),
121
- merge = this.value,
122
- reverse_key = this.name;
123
-
124
- while((k = keys.pop()) !== undefined){
125
-
126
- // adjust reverse_key
127
- reverse_key = reverse_key.replace(new RegExp("\\[" + k + "\\]$"), '');
128
-
129
- // push
130
- if(k.match(patterns.push)){
131
- merge = self.build([], self.push_counter(reverse_key), merge);
132
- }
133
- // fixed
134
- else if(k.match(patterns.fixed)){
135
- merge = self.build([], k, merge);
136
- }
137
- // named
138
- else if(k.match(patterns.named)){
139
- merge = self.build({}, k, merge);
140
- }
141
- }
142
- json = $.extend(true, json, merge);
143
- });
144
- return json;
145
- };
146
- });
89
+ }).call(this);
@@ -6,6 +6,7 @@ $(function() {
6
6
  */
7
7
  function scroll_if_anchor(href) {
8
8
  href = typeof(href) == "string" ? href : $(this).attr("href");
9
+ if (typeof(href) == 'undefined') return
9
10
 
10
11
  var fromTop = 0;
11
12
 
@@ -0,0 +1,54 @@
1
+ $.fn.serializeObject = function() {
2
+ var self = this,
3
+ json = {},
4
+ push_counters = {},
5
+ patterns = {
6
+ "validate": /^[a-zA-Z][a-zA-Z0-9_\-]*(?:\[(?:\d*|[a-zA-Z0-9_\-]+)\])*$/,
7
+ "key": /[a-zA-Z0-9_\-]+|(?=\[\])/g,
8
+ "push": /^$/,
9
+ "fixed": /^\d+$/,
10
+ "named": /^[a-zA-Z0-9_\-]+$/
11
+ };
12
+
13
+ this.build = function(base, key, value){
14
+ base[key] = value;
15
+ return base;
16
+ };
17
+ this.push_counter = function(key){
18
+ if(push_counters[key] === undefined){
19
+ push_counters[key] = 0;
20
+ }
21
+ return push_counters[key]++;
22
+ };
23
+ $.each($(this).serializeArray(), function(){
24
+ // skip invalid keys
25
+ if(!patterns.validate.test(this.name)) {
26
+ return;
27
+ }
28
+ var k,
29
+ keys = this.name.match(patterns.key),
30
+ merge = this.value,
31
+ reverse_key = this.name;
32
+
33
+ while((k = keys.pop()) !== undefined){
34
+
35
+ // adjust reverse_key
36
+ reverse_key = reverse_key.replace(new RegExp("\\[" + k + "\\]$"), '');
37
+
38
+ // push
39
+ if(k.match(patterns.push)){
40
+ merge = self.build([], self.push_counter(reverse_key), merge);
41
+ }
42
+ // fixed
43
+ else if(k.match(patterns.fixed)){
44
+ merge = self.build([], k, merge);
45
+ }
46
+ // named
47
+ else if(k.match(patterns.named)){
48
+ merge = self.build({}, k, merge);
49
+ }
50
+ }
51
+ json = $.extend(true, json, merge);
52
+ });
53
+ return json;
54
+ };
@@ -1,70 +1,44 @@
1
- .hidden {
2
- display: none;
3
- }
4
-
5
- .params .params {
6
- margin-left: 30px;
7
- }
8
-
9
- .param-inputs .param-inputs {
10
- margin-left: 30px;
11
- }
12
-
13
- .param-inputs fieldset {
14
- border: none;
15
- padding: 0;
16
- }
17
- .param-inputs input {
18
- border: none;
19
- }
20
- h3 input {
21
- border: none;
22
- width: 120px;
23
- text-align: center;
24
- }
25
-
26
- legend {
27
- margin-bottom: 5px;
28
- }
29
- .param-inputs legend {
30
- font-size: 100%;
31
- border: none;
32
- margin-bottom: 0;
33
- }
34
- label {
35
- font-weight: normal;
36
- margin-bottom: 0;
37
- }
38
-
39
- .response {
1
+ .api-explorer .hidden {
2
+ display: none; }
3
+ .api-explorer .params .params {
4
+ margin-left: 30px; }
5
+ .api-explorer .param-inputs .param-inputs {
6
+ margin-left: 30px; }
7
+ .api-explorer .param-inputs fieldset {
8
+ border: none;
9
+ padding: 0; }
10
+ .api-explorer .param-inputs input {
11
+ border: none; }
12
+ .api-explorer h3 input {
13
+ border: none;
14
+ width: 120px;
15
+ text-align: center; }
16
+ .api-explorer legend {
17
+ margin-bottom: 5px; }
18
+ .api-explorer .param-inputs legend {
19
+ font-size: 100%;
20
+ border: none;
21
+ margin-bottom: 0; }
22
+ .api-explorer label {
23
+ font-weight: normal;
24
+ margin-bottom: 0; }
25
+ .api-explorer .response {
40
26
  border: solid 1px black;
41
27
  background-color: #CCC;
42
28
  padding: 20px;
43
29
  max-height: 400px;
44
- overflow: scroll;
45
- }
46
-
47
- .description {
48
- font-size: 80%;
49
- }
50
-
51
- fieldset {
52
- margin-bottom: 5px;
53
- }
54
-
55
- legend, label {
56
- position: relative;
57
- }
58
-
59
- .send-toggle {
60
- font-size: 80%;
61
- cursor: pointer;
62
- display: none;
63
- }
64
- :hover > .send-toggle {
65
- display: inline;
66
- }
67
-
68
- .strikethrough {
69
- text-decoration: line-through;
70
- }
30
+ overflow: scroll; }
31
+ .api-explorer .description {
32
+ font-size: 80%; }
33
+ .api-explorer fieldset {
34
+ margin-bottom: 5px; }
35
+ .api-explorer legend, .api-explorer label {
36
+ position: relative; }
37
+ .api-explorer .send-toggle {
38
+ font-size: 80%;
39
+ cursor: pointer;
40
+ display: none; }
41
+ .api-explorer :hover > .send-toggle {
42
+ display: inline; }
43
+ .api-explorer .strikethrough {
44
+ text-decoration: line-through; }
@@ -1,20 +1,22 @@
1
- <script type="text/javascript">
2
- window.api_explorer_base_url = "<%= @description.base_url %>";
3
- </script>
1
+ <div class="api-explorer">
2
+ <script type="text/javascript">
3
+ window.api_explorer_base_url = "<%= @description.base_url %>";
4
+ </script>
4
5
 
5
- <%= stylesheet_link_tag "api_explorer/application", media: "all" %>
6
- <%= javascript_include_tag "api_explorer/application" %>
6
+ <%= stylesheet_link_tag "api_explorer/application", media: "all" %>
7
+ <%= javascript_include_tag "api_explorer/application" %>
7
8
 
8
- <div class="row">
9
- <div class="col-sm-12">
10
- <h1>Index</h1>
11
- <ul class="contents">
12
- <% @description.children.each_with_index do |ch, i| %>
13
- <%= render 'content_node', node: ch, id: i %>
14
- <% end %>
15
- </ul>
9
+ <div class="row">
10
+ <div class="col-sm-12">
11
+ <h1>Index</h1>
12
+ <ul class="contents">
13
+ <% @description.children.each_with_index do |ch, i| %>
14
+ <%= render 'content_node', node: ch, id: i %>
15
+ <% end %>
16
+ </ul>
17
+ </div>
16
18
  </div>
17
- </div>
18
19
 
19
- <%= render 'shared' %>
20
- <%= render 'node', node: @description, id: "", depth: 0 %>
20
+ <%= render 'shared' %>
21
+ <%= render 'node', node: @description, id: "", depth: 0 %>
22
+ </div>
@@ -1,3 +1,3 @@
1
1
  module ApiExplorer
2
- VERSION = "0.0.1.pre.1"
2
+ VERSION = "0.0.2.pre.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_api_explorer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.pre.1
4
+ version: 0.0.2.pre.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Max Hollmann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-08 00:00:00.000000000 Z
11
+ date: 2015-03-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -52,6 +52,34 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: coffee-script
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sass
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
55
83
  description: ''
56
84
  email:
57
85
  - maxhollmann@gmail.com
@@ -60,10 +88,12 @@ extensions: []
60
88
  extra_rdoc_files: []
61
89
  files:
62
90
  - MIT-LICENSE
91
+ - README.md
63
92
  - Rakefile
64
93
  - app/assets/javascripts/api_explorer/application.js
65
94
  - app/assets/javascripts/api_explorer/explorer.js
66
95
  - app/assets/javascripts/api_explorer/scroll.js
96
+ - app/assets/javascripts/api_explorer/serialize_object.js
67
97
  - app/assets/stylesheets/api_explorer/application.css
68
98
  - app/assets/stylesheets/api_explorer/explorer.css
69
99
  - app/controllers/api_explorer/application_controller.rb
@@ -121,6 +151,6 @@ rubyforge_project:
121
151
  rubygems_version: 2.2.2
122
152
  signing_key:
123
153
  specification_version: 4
124
- summary: Provides a simple DSL to describe your API, and let's you mount an interactive
154
+ summary: Provides a simple DSL to describe your JSON API, and let's you mount an interactive
125
155
  sandbox to explore and test it.
126
156
  test_files: []