diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 8c18d39..1908db1 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -23,7 +23,13 @@ class Configuration implements ConfigurationInterface ->root('nelmio_api_doc') ->children() ->scalarNode('name')->defaultValue('API documentation')->end() - ->scalarNode('sandbox_target')->defaultValue('/app_dev.php')->end() + ->arrayNode('sandbox') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('enabled')->defaultTrue()->end() + ->scalarNode('endpoint')->defaultValue('/app_dev.php')->end() + ->end() + ->end() ->end(); return $treeBuilder; diff --git a/DependencyInjection/NelmioApiDocExtension.php b/DependencyInjection/NelmioApiDocExtension.php index 0aad910..1ecae53 100644 --- a/DependencyInjection/NelmioApiDocExtension.php +++ b/DependencyInjection/NelmioApiDocExtension.php @@ -29,7 +29,8 @@ class NelmioApiDocExtension extends Extension $config = $processor->processConfiguration($configuration, $configs); $container->setParameter('nelmio_api_doc.api_name', $config['name']); - $container->setParameter('nelmio_api_doc.api_sandbox_target', $config['sandbox_target']); + $container->setParameter('nelmio_api_doc.sandbox.enabled', $config['sandbox']['enabled']); + $container->setParameter('nelmio_api_doc.sandbox.endpoint', $config['sandbox']['endpoint']); $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('formatters.xml'); diff --git a/Formatter/HtmlFormatter.php b/Formatter/HtmlFormatter.php index 8461b7f..71edc7b 100644 --- a/Formatter/HtmlFormatter.php +++ b/Formatter/HtmlFormatter.php @@ -23,7 +23,12 @@ class HtmlFormatter extends AbstractFormatter /** * @var string */ - private $sandboxTarget; + private $endpoint; + + /** + * @var boolean + */ + private $enableSandbox; /** * @var \Symfony\Component\Templating\EngineInterface @@ -39,11 +44,19 @@ class HtmlFormatter extends AbstractFormatter } /** - * @param string $sandboxTarget + * @param string $endpoint */ - public function setSandboxTarget($sandboxTarget) + public function setEndpoint($endpoint) { - $this->sandboxTarget = $sandboxTarget; + $this->endpoint = $endpoint; + } + + /** + * @param boolean $enableSandbox + */ + public function setEnableSandbox($enableSandbox) + { + $this->enableSandbox = $enableSandbox; } /** @@ -83,7 +96,8 @@ class HtmlFormatter extends AbstractFormatter { return array( 'apiName' => $this->apiName, - 'sandboxTarget' => $this->sandboxTarget, + 'endpoint' => $this->endpoint, + 'enableSandbox' => $this->enableSandbox, 'date' => date(DATE_RFC822), 'css' => file_get_contents(__DIR__ . '/../Resources/public/css/screen.css'), 'js' => file_get_contents(__DIR__ . '/../Resources/public/js/all.js'), diff --git a/README.md b/README.md index 07f2f15..fc7ea26 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,15 @@ You can specify your own API name: nelmio_api_doc: name: My API +This bundle provides a sandbox mode in order to test API methods. You can +configure this sandbox using the following parameters: + + # app/config/config.yml + nelmio_api_doc: + sandbox: + enabled: true # default: true, you can set this parameter to `false` to disable the sandbox + endpoint: http://sandbox.example.com/ # default: /app_dev.php, use this parameter to define which URL to call through the sandbox + ## Credits ## diff --git a/Resources/config/formatters.xml b/Resources/config/formatters.xml index 1bf12c9..80a6601 100644 --- a/Resources/config/formatters.xml +++ b/Resources/config/formatters.xml @@ -30,8 +30,11 @@ %nelmio_api_doc.api_name% - - %nelmio_api_doc.api_sandbox_target% + + %nelmio_api_doc.sandbox.enabled% + + + %nelmio_api_doc.sandbox.endpoint% diff --git a/Resources/views/layout.html.twig b/Resources/views/layout.html.twig index fe72458..aa4768c 100644 --- a/Resources/views/layout.html.twig +++ b/Resources/views/layout.html.twig @@ -29,233 +29,235 @@ $(this).next().slideToggle('slow'); }); - var toggleButtonText = function ($btn) { - if ($btn.text() === 'Default') { - $btn.text('Raw'); - } else { - $btn.text('Default'); - } - }; - - var renderRawBody = function ($container) { - var rawData, $btn; - - rawData = $container.data('raw-response'); - $btn = $container.parents('.pane').find('.to-raw'); - - $container.addClass('prettyprinted'); - $container.html(rawData); - - $btn.removeClass('to-raw'); - $btn.addClass('to-prettify'); - - toggleButtonText($btn); - }; - - var renderPrettifiedBody = function ($container) { - var rawData, $btn; - - rawData = $container.data('raw-response'); - $btn = $container.parents('.pane').find('.to-prettify'); - - $container.removeClass('prettyprinted'); - $container.html(prettifyResponse(rawData)); - prettyPrint && prettyPrint(); - - $btn.removeClass('to-prettify'); - $btn.addClass('to-raw'); - - toggleButtonText($btn); - }; - - $('.tabs li').click(function() { - var contentGroup = $(this).parents('.content'); - - $('.pane.selected', contentGroup).removeClass('selected'); - $('.pane.' + $(this).data('pane'), contentGroup).addClass('selected'); - - $('li', $(this).parent()).removeClass('selected'); - $(this).addClass('selected'); - }); - - var prettifyResponse = function(text) { - try { - var data = typeof text === 'string' ? JSON.parse(text) : text; - text = JSON.stringify(data, undefined, ' '); - } catch (err) { - } - - // HTML encode the result - return $('
').text(text).html(); - }; - - var displayFinalUrl = function(xhr, method, url, container) { - container.html(method + ' ' + url); - }; - - var displayResponseData = function(xhr, container) { - var data = xhr.responseText; - - container.data('raw-response', data); - - if ('<' === data[0]) { - renderRawBody(container); + {% if enableSandbox %} + var toggleButtonText = function ($btn) { + if ($btn.text() === 'Default') { + $btn.text('Raw'); } else { - renderPrettifiedBody(container); + $btn.text('Default'); } - - container.parents('.pane').find('.to-prettify').text('Raw'); - container.parents('.pane').find('.to-raw').text('Raw'); - }; - - var displayResponseHeaders = function(xhr, container) { - var text = xhr.status + ' ' + xhr.statusText + "\n\n"; - text += xhr.getAllResponseHeaders(); - - container.html(text); - }; - - var displayResponse = function(xhr, method, url, result_container) { - displayFinalUrl(xhr, method, url, $('.url', result_container)); - displayResponseData(xhr, $('.response', result_container)); - displayResponseHeaders(xhr, $('.headers', result_container)); - - result_container.show(); - }; - - $('.pane.sandbox form').submit(function() { - var url = $(this).attr('action'), - method = $(this).attr('method'), - self = this, - params = { - _format: 'json' // default format if not overriden in the form - }, - headers = {}, - content = $(this).find('textarea.content').val(); - result_container = $('.result', $(this).parent()); - - // retrieve all the parameters to send - $('.parameters .tuple', $(this)).each(function() { - var key, value; - - key = $('.key', $(this)).val(); - value = $('.value', $(this)).val(); - - if (value) { - params[key] = value; - } - }); - - // retrieve the additional headers to send - $('.headers .tuple', $(this)).each(function() { - var key, value; - - key = $('.key', $(this)).val(); - value = $('.value', $(this)).val(); - - if (value) { - headers[key] = value; - } - }); - - // fix parameters in URL - for (var key in $.extend({}, params)) { - if (url.indexOf('{' + key + '}') !== -1) { - url = url.replace('{' + key + '}', params[key]); - delete params[key]; - } }; - // disable all the fiels and buttons - $('input, button', $(this)).attr('disabled', 'disabled'); + var renderRawBody = function ($container) { + var rawData, $btn; - // and trigger the API call - $.ajax({ - url: '{{ sandboxTarget }}' + url, - type: method, - data: content.length ? content : params, - headers: headers, - complete: function(xhr) { - displayResponse(xhr, method, url, result_container); + rawData = $container.data('raw-response'); + $btn = $container.parents('.pane').find('.to-raw'); - // and enable them back - $('input:not(.content-type), button', $(self)).removeAttr('disabled'); - } + $container.addClass('prettyprinted'); + $container.html(rawData); + + $btn.removeClass('to-raw'); + $btn.addClass('to-prettify'); + + toggleButtonText($btn); + }; + + var renderPrettifiedBody = function ($container) { + var rawData, $btn; + + rawData = $container.data('raw-response'); + $btn = $container.parents('.pane').find('.to-prettify'); + + $container.removeClass('prettyprinted'); + $container.html(prettifyResponse(rawData)); + prettyPrint && prettyPrint(); + + $btn.removeClass('to-prettify'); + $btn.addClass('to-raw'); + + toggleButtonText($btn); + }; + + $('.tabs li').click(function() { + var contentGroup = $(this).parents('.content'); + + $('.pane.selected', contentGroup).removeClass('selected'); + $('.pane.' + $(this).data('pane'), contentGroup).addClass('selected'); + + $('li', $(this).parent()).removeClass('selected'); + $(this).addClass('selected'); }); - return false; - }); - - $('.pane.sandbox').delegate('.to-raw', 'click', function(e) { - renderRawBody($(this).parents('.pane').find('.response')); - - e.preventDefault(); - }); - - $('.pane.sandbox').delegate('.to-prettify', 'click', function(e) { - renderPrettifiedBody($(this).parents('.pane').find('.response')); - - e.preventDefault(); - }); - - $('.pane.sandbox').delegate('.to-expand, .to-shrink', 'click', function(e) { - var $headers = $(this).parents('.result').find('.headers'); - var $label = $(this).parents('.result').find('a.to-expand'); - - if ($headers.hasClass('to-expand')) { - $headers.removeClass('to-expand'); - $headers.addClass('to-shrink'); - $label.text('Shrink'); - } else { - $headers.removeClass('to-shrink'); - $headers.addClass('to-expand'); - $label.text('Expand'); - } - - e.preventDefault(); - }); - - $('.pane.sandbox').on('click', '.add', function() { - var html = $(this).parents('.pane').find('.tuple_template').html(); - - $(this).before(html); - - return false; - }); - - $('.pane.sandbox').on('click', '.remove', function() { - $(this).parent().remove(); - }); - - $('.pane.sandbox').on('click', '.set-content-type', function(e) { - var html; - var $element; - var $headers = $(this).parents('form').find('.headers'); - var content_type = $(this).prev('input.value').val(); - - e.preventDefault(); - - if (content_type.length === 0) { - return; - } - - $headers.find('input.key').each(function() { - if ($.trim($(this).val().toLowerCase()) === 'content-type') { - $element = $(this).parents('p'); - return false; + var prettifyResponse = function(text) { + try { + var data = typeof text === 'string' ? JSON.parse(text) : text; + text = JSON.stringify(data, undefined, ' '); + } catch (err) { } + + // HTML encode the result + return $('
').text(text).html(); + }; + + var displayFinalUrl = function(xhr, method, url, container) { + container.html(method + ' ' + url); + }; + + var displayResponseData = function(xhr, container) { + var data = xhr.responseText; + + container.data('raw-response', data); + + if ('<' === data[0]) { + renderRawBody(container); + } else { + renderPrettifiedBody(container); + } + + container.parents('.pane').find('.to-prettify').text('Raw'); + container.parents('.pane').find('.to-raw').text('Raw'); + }; + + var displayResponseHeaders = function(xhr, container) { + var text = xhr.status + ' ' + xhr.statusText + "\n\n"; + text += xhr.getAllResponseHeaders(); + + container.html(text); + }; + + var displayResponse = function(xhr, method, url, result_container) { + displayFinalUrl(xhr, method, url, $('.url', result_container)); + displayResponseData(xhr, $('.response', result_container)); + displayResponseHeaders(xhr, $('.headers', result_container)); + + result_container.show(); + }; + + $('.pane.sandbox form').submit(function() { + var url = $(this).attr('action'), + method = $(this).attr('method'), + self = this, + params = { + _format: 'json' // default format if not overriden in the form + }, + headers = {}, + content = $(this).find('textarea.content').val(); + result_container = $('.result', $(this).parent()); + + // retrieve all the parameters to send + $('.parameters .tuple', $(this)).each(function() { + var key, value; + + key = $('.key', $(this)).val(); + value = $('.value', $(this)).val(); + + if (value) { + params[key] = value; + } + }); + + // retrieve the additional headers to send + $('.headers .tuple', $(this)).each(function() { + var key, value; + + key = $('.key', $(this)).val(); + value = $('.value', $(this)).val(); + + if (value) { + headers[key] = value; + } + }); + + // fix parameters in URL + for (var key in $.extend({}, params)) { + if (url.indexOf('{' + key + '}') !== -1) { + url = url.replace('{' + key + '}', params[key]); + delete params[key]; + } + }; + + // disable all the fiels and buttons + $('input, button', $(this)).attr('disabled', 'disabled'); + + // and trigger the API call + $.ajax({ + url: '{{ endpoint }}' + url, + type: method, + data: content.length ? content : params, + headers: headers, + complete: function(xhr) { + displayResponse(xhr, method, url, result_container); + + // and enable them back + $('input:not(.content-type), button', $(self)).removeAttr('disabled'); + } + }); + + return false; }); - if (typeof $element === 'undefined') { - html = $(this).parents('.pane').find('.tuple_template').html(); + $('.pane.sandbox').delegate('.to-raw', 'click', function(e) { + renderRawBody($(this).parents('.pane').find('.response')); - $element = $headers.find('legend').after(html).next('p'); - } + e.preventDefault(); + }); - $element.find('input.key').val('Content-Type'); - $element.find('input.value').val(content_type); + $('.pane.sandbox').delegate('.to-prettify', 'click', function(e) { + renderPrettifiedBody($(this).parents('.pane').find('.response')); - }); + e.preventDefault(); + }); + + $('.pane.sandbox').delegate('.to-expand, .to-shrink', 'click', function(e) { + var $headers = $(this).parents('.result').find('.headers'); + var $label = $(this).parents('.result').find('a.to-expand'); + + if ($headers.hasClass('to-expand')) { + $headers.removeClass('to-expand'); + $headers.addClass('to-shrink'); + $label.text('Shrink'); + } else { + $headers.removeClass('to-shrink'); + $headers.addClass('to-expand'); + $label.text('Expand'); + } + + e.preventDefault(); + }); + + $('.pane.sandbox').on('click', '.add', function() { + var html = $(this).parents('.pane').find('.tuple_template').html(); + + $(this).before(html); + + return false; + }); + + $('.pane.sandbox').on('click', '.remove', function() { + $(this).parent().remove(); + }); + + $('.pane.sandbox').on('click', '.set-content-type', function(e) { + var html; + var $element; + var $headers = $(this).parents('form').find('.headers'); + var content_type = $(this).prev('input.value').val(); + + e.preventDefault(); + + if (content_type.length === 0) { + return; + } + + $headers.find('input.key').each(function() { + if ($.trim($(this).val().toLowerCase()) === 'content-type') { + $element = $(this).parents('p'); + return false; + } + }); + + if (typeof $element === 'undefined') { + html = $(this).parents('.pane').find('.tuple_template').html(); + + $element = $headers.find('legend').after(html).next('p'); + } + + $element.find('input.key').val('Content-Type'); + $element.find('input.value').val(content_type); + + }); + {% endif %} diff --git a/Resources/views/method.html.twig b/Resources/views/method.html.twig index 5610ae8..d749cd4 100644 --- a/Resources/views/method.html.twig +++ b/Resources/views/method.html.twig @@ -1,186 +1,190 @@
  • -

    - - {{ data.method|upper }} - - - {{ data.uri }} - -

    -
      - {% if data.description is defined %} -
    • {{ data.description }}
    • - {% endif %} -
    +

    + + {{ data.method|upper }} + + + {{ data.uri }} + +

    +
      + {% if data.description is defined %} +
    • {{ data.description }}
    • + {% endif %} +
    -
      -
    • Documentation
    • -
    • Sandbox
    • -
    +
      +
    • Documentation
    • + {% if enableSandbox %} +
    • Sandbox
    • + {% endif %} +
    -
    -
    - {% if data.documentation is defined and data.documentation is not empty %} -

    Documentation

    -
    {{ data.documentation|markdown }}
    - {% endif %} +
    +
    + {% if data.documentation is defined and data.documentation is not empty %} +

    Documentation

    +
    {{ data.documentation|markdown }}
    + {% endif %} - {% if data.requirements is defined and data.requirements is not empty %} -

    Requirements

    - - - - - - - - - - - {% for name, infos in data.requirements %} + {% if data.requirements is defined and data.requirements is not empty %} +

    Requirements

    +
    NameRequirementTypeDescription
    + + + + + + + + + + {% for name, infos in data.requirements %} + + + + + + + {% endfor %} + +
    NameRequirementTypeDescription
    {{ name }}{{ infos.requirement }}{{ infos.type }}{{ infos.description }}
    + {% endif %} + + {% if data.filters is defined and data.filters is not empty %} +

    Filters

    + + + + + + + + + {% for name, infos in data.filters %} - - - + {% endfor %} - -
    NameInformation
    {{ name }}{{ infos.requirement }}{{ infos.type }}{{ infos.description }} + + {% for key, value in infos %} + + + + + {% endfor %} +
    {{ key|title }}{{ value|json_encode|trim('"') }}
    +
    - {% endif %} + + + {% endif %} - {% if data.filters is defined and data.filters is not empty %} -

    Filters

    - - - - - - - - - {% for name, infos in data.filters %} - - - - - {% endfor %} - -
    NameInformation
    {{ name }} - - {% for key, value in infos %} - - - - - {% endfor %} -
    {{ key|title }}{{ value|json_encode|trim('"') }}
    -
    - {% endif %} - - {% if data.parameters is defined and data.parameters is not empty %} -

    Parameters

    - - - - - - - - - - - {% for name, infos in data.parameters %} + {% if data.parameters is defined and data.parameters is not empty %} +

    Parameters

    +
    ParameterTypeRequired?Description
    + - - - - + + + + - {% endfor %} - -
    {{ name }}{{ infos.dataType }}{{ infos.required ? 'true' : 'false' }}{{ infos.description }}ParameterTypeRequired?Description
    - {% endif %} -
    - -
    -
    -
    - Parameters - {% if data.parameters is defined %} + + {% for name, infos in data.parameters %} -

    - - = - - -

    + + {{ name }} + {{ infos.dataType }} + {{ infos.required ? 'true' : 'false' }} + {{ infos.description }} + {% endfor %} - {% endif %} - {% if data.filters is defined %} - {% for name, infos in data.filters %} -

    - - = - - -

    - {% endfor %} - {% endif %} - - -
    - -
    - Headers - -

    - - = - - -

    - - -
    - -
    - Content - - - -

    - - = - - Replaces header if set -

    -
    - -
    - -
    -
    - - - -
    -

    Request URL

    -
    
    -
    -                

    Response Headers [Expand]

    -
    
    -
    -                

    Response Body [Raw]

    -
    
    +                    
    +                
    +            {% endif %}
                 
    + + {% if enableSandbox %} +
    +
    +
    + Parameters + {% if data.parameters is defined %} + {% for name, infos in data.parameters %} +

    + + = + - +

    + {% endfor %} + {% endif %} + {% if data.filters is defined %} + {% for name, infos in data.filters %} +

    + + = + - +

    + {% endfor %} + {% endif %} + + +
    + +
    + Headers + +

    + + = + - +

    + + +
    + +
    + Content + + + +

    + + = + + Replaces header if set +

    +
    + +
    + +
    +
    + + + +
    +

    Request URL

    +
    
    +
    +                        

    Response Headers [Expand]

    +
    
    +
    +                        

    Response Body [Raw]

    +
    
    +                    
    +
    + {% endif %}
    -