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

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 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: []