haveapi 0.31.1 → 0.32.0

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.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/benchmarks/documentation.rb +29 -6
  4. data/benchmarks/endpoints.rb +153 -24
  5. data/benchmarks/spec/client_examples_spec.rb +109 -37
  6. data/doc/client-examples.md +218 -45
  7. data/doc/documentation-oauth.md +157 -0
  8. data/doc/documentation-ui.md +192 -0
  9. data/doc/index.md +3 -6
  10. data/doc/performance.md +39 -12
  11. data/haveapi.gemspec +1 -1
  12. data/lib/haveapi/authentication/chain.rb +19 -0
  13. data/lib/haveapi/authentication/oauth2/provider.rb +7 -1
  14. data/lib/haveapi/client_example.rb +143 -15
  15. data/lib/haveapi/client_examples/curl.rb +30 -20
  16. data/lib/haveapi/client_examples/dynamic_helpers.rb +1 -1
  17. data/lib/haveapi/client_examples/go_client.rb +9 -2
  18. data/lib/haveapi/client_examples/http.rb +9 -4
  19. data/lib/haveapi/client_examples/js_async_client.rb +7 -0
  20. data/lib/haveapi/client_examples/js_client.rb +47 -36
  21. data/lib/haveapi/client_examples/php_client.rb +161 -75
  22. data/lib/haveapi/client_examples/python_async_client.rb +3 -0
  23. data/lib/haveapi/client_examples/python_client.rb +61 -47
  24. data/lib/haveapi/client_examples/ruby_cli.rb +27 -18
  25. data/lib/haveapi/client_examples/ruby_client.rb +80 -31
  26. data/lib/haveapi/client_examples/rust_client.rb +20 -9
  27. data/lib/haveapi/client_examples/ts_async_client.rb +7 -0
  28. data/lib/haveapi/client_examples/ts_client.rb +3 -0
  29. data/lib/haveapi/client_examples/ts_generated_client.rb +10 -2
  30. data/lib/haveapi/documentation_context.rb +7 -2
  31. data/lib/haveapi/documentation_helpers.rb +223 -0
  32. data/lib/haveapi/documentation_oauth.rb +399 -0
  33. data/lib/haveapi/documentation_search.rb +225 -0
  34. data/lib/haveapi/documentation_search_index.rb +79 -0
  35. data/lib/haveapi/documentation_validators.rb +179 -0
  36. data/lib/haveapi/public/css/documentation.css +343 -0
  37. data/lib/haveapi/public/js/documentation.js +992 -0
  38. data/lib/haveapi/server.rb +144 -86
  39. data/lib/haveapi/shared_documentation_helpers.rb +54 -0
  40. data/lib/haveapi/version.rb +1 -1
  41. data/lib/haveapi/views/doc_layout.erb +16 -0
  42. data/lib/haveapi/views/doc_sidebars/create-client.erb +3 -3
  43. data/lib/haveapi/views/doc_sidebars/json-schema.erb +1 -1
  44. data/lib/haveapi/views/doc_sidebars/protocol.erb +5 -5
  45. data/lib/haveapi/views/documentation/search_form.erb +10 -0
  46. data/lib/haveapi/views/documentation/search_index.erb +1 -0
  47. data/lib/haveapi/views/documentation/search_results.erb +20 -0
  48. data/lib/haveapi/views/index.erb +2 -4
  49. data/lib/haveapi/views/main_layout.erb +12 -6
  50. data/lib/haveapi/views/shared_documentation.erb +12 -0
  51. data/lib/haveapi/views/shared_sidebar.erb +25 -0
  52. data/lib/haveapi/views/version_page/auth_body.erb +12 -33
  53. data/lib/haveapi/views/version_page/client_overview.erb +40 -0
  54. data/lib/haveapi/views/version_page/example_panels.erb +18 -0
  55. data/lib/haveapi/views/version_page/resource_body.erb +91 -132
  56. data/lib/haveapi/views/version_page/resource_directory.erb +14 -0
  57. data/lib/haveapi/views/version_page/usage.erb +7 -0
  58. data/lib/haveapi/views/version_page.erb +36 -117
  59. data/lib/haveapi/views/version_sidebar/auth_nav.erb +12 -16
  60. data/lib/haveapi/views/version_sidebar/auth_resource_nav.erb +19 -0
  61. data/lib/haveapi/views/version_sidebar/authentication_control.erb +26 -0
  62. data/lib/haveapi/views/version_sidebar/authentication_status.erb +3 -0
  63. data/lib/haveapi/views/version_sidebar/brand.erb +31 -0
  64. data/lib/haveapi/views/version_sidebar/resource_nav.erb +11 -20
  65. data/lib/haveapi/views/version_sidebar.erb +59 -48
  66. data/spec/authentication/basic_spec.rb +10 -10
  67. data/spec/authentication/oauth2_chain_spec.rb +72 -0
  68. data/spec/authentication/oauth2_token_validation_spec.rb +38 -0
  69. data/spec/client_examples/assets/documentation_test.cjs +817 -0
  70. data/spec/client_examples/context_performance_spec.rb +5 -4
  71. data/spec/client_examples/context_spec.rb +23 -2
  72. data/spec/client_examples/detail_spec.rb +175 -0
  73. data/spec/client_examples/dynamic_acceptance.md +59 -26
  74. data/spec/client_examples/dynamic_acceptance.rb +164 -44
  75. data/spec/client_examples/dynamic_acceptance_spec.rb +140 -23
  76. data/spec/client_examples/dynamic_fixture.rb +4 -3
  77. data/spec/client_examples/dynamic_js_spec.rb +20 -11
  78. data/spec/client_examples/dynamic_metadata_spec.rb +69 -0
  79. data/spec/client_examples/dynamic_mounted_paths_spec.rb +9 -7
  80. data/spec/client_examples/dynamic_python_spec.rb +62 -11
  81. data/spec/client_examples/go_spec.rb +14 -1
  82. data/spec/client_examples/presentation_spec.rb +247 -0
  83. data/spec/client_examples/profile_mount_spec.rb +40 -13
  84. data/spec/client_examples/rust_client_spec.rb +35 -1
  85. data/spec/client_examples/shell_spec.rb +268 -0
  86. data/spec/client_examples/syntax_spec.rb +192 -0
  87. data/spec/client_examples/ts_generated_client_spec.rb +15 -2
  88. data/spec/client_examples/typescript_profiles_spec.rb +1 -1
  89. data/spec/documentation/auth_filtering_spec.rb +9 -4
  90. data/spec/documentation/canonical_routes_spec.rb +89 -0
  91. data/spec/documentation/client_examples_spec.rb +39 -5
  92. data/spec/documentation/client_overview_spec.rb +145 -0
  93. data/spec/documentation/current_user_html_escaping_spec.rb +7 -2
  94. data/spec/documentation/example_variants_spec.rb +93 -0
  95. data/spec/documentation/fixtures/search_index.json +476 -0
  96. data/spec/documentation/identity_and_guides_spec.rb +130 -0
  97. data/spec/documentation/identity_context_spec.rb +180 -0
  98. data/spec/documentation/navigation_spec.rb +296 -0
  99. data/spec/documentation/oauth_fixture_spec.rb +39 -0
  100. data/spec/documentation/oauth_spec.rb +912 -0
  101. data/spec/documentation/oauth_transport_spec.rb +45 -0
  102. data/spec/documentation/performance_spec.rb +47 -6
  103. data/spec/documentation/reference_headings_spec.rb +190 -0
  104. data/spec/documentation/search_index_routes_spec.rb +202 -0
  105. data/spec/documentation/search_index_spec.rb +85 -0
  106. data/spec/documentation/search_routes_spec.rb +199 -0
  107. data/spec/documentation/search_spec.rb +134 -0
  108. data/spec/documentation/shared_pages_spec.rb +240 -0
  109. data/spec/documentation/validators_spec.rb +123 -0
  110. data/spec/documentation_spec.rb +3 -3
  111. data/spec/packaging_spec.rb +21 -2
  112. data/spec/performance/documentation_identity_fixture_spec.rb +47 -0
  113. data/spec/performance/documentation_shapes_spec.rb +97 -0
  114. data/spec/performance/json_activation_spec.rb +78 -1
  115. data/spec/performance/release_lineage_spec.rb +4 -3
  116. data/spec/performance/runner_spec.rb +1 -0
  117. data/test_support/all_client_performance_fixture.rb +6 -10
  118. data/test_support/documentation_oauth_fixture.rb +54 -0
  119. data/test_support/documentation_performance_fixture.rb +19 -1
  120. data/test_support/dynamic_documentation_api.rb +35 -10
  121. data/test_support/dynamic_python_login_spec.rb +3 -11
  122. metadata +52 -3
@@ -4,13 +4,18 @@ require 'tilt'
4
4
  require 'cgi'
5
5
  require 'haveapi/hooks'
6
6
  require 'haveapi/documentation_clients'
7
+ require 'haveapi/documentation_helpers'
8
+ require 'haveapi/documentation_validators'
9
+ require 'haveapi/documentation_search'
10
+ require 'haveapi/shared_documentation_helpers'
11
+ require 'haveapi/documentation_oauth'
7
12
 
8
13
  module HaveAPI
9
14
  class Server
10
15
  attr_accessor :default_version, :action_state, :validation_error_http_status,
11
16
  :default_locale, :parameter_i18n_scope
12
17
  attr_reader :root, :routes, :module_name, :auth_chain, :versions, :extensions,
13
- :action_state_auth, :locale_header, :available_locales
18
+ :action_state_auth, :locale_header, :available_locales, :documentation_name
14
19
 
15
20
  include Hookable
16
21
 
@@ -231,13 +236,8 @@ module HaveAPI
231
236
  settings.api_server.root
232
237
  end
233
238
 
234
- def logout_url
235
- ret = url("#{root}_logout")
236
- ret.insert(ret.index('//') + 2, '_log:out@')
237
- end
238
-
239
239
  def doc(file)
240
- markdown :"../../../doc/#{file}"
240
+ markdown :"../../../doc/#{file}", with_toc_data: true, tables: true, fenced_code_blocks: true, lax_spacing: true
241
241
  end
242
242
 
243
243
  def base_url
@@ -260,7 +260,17 @@ module HaveAPI
260
260
  profile = if client
261
261
  settings.api_server.documentation_client_profiles(@v)[client.code&.to_sym]
262
262
  end
263
- @documentation_context.with(profile:, **changes)
263
+ parent = @documentation_context
264
+ # Snapshot each selected resource once in this request, before client
265
+ # profile/detail derivation, and share it across that resource's panels.
266
+ if changes[:resource] && changes[:resource_path]
267
+ resource = changes.delete(:resource)
268
+ path = changes.delete(:resource_path).map { |part| part.to_s.dup.freeze }.freeze
269
+ @documentation_resource_contexts ||= {}
270
+ key = [resource.__id__, path]
271
+ parent = @documentation_resource_contexts[key] ||= parent.with(resource_path: path, resource:)
272
+ end
273
+ parent.with(profile:, **changes)
264
274
  end
265
275
 
266
276
  def documentation_clients
@@ -274,6 +284,27 @@ module HaveAPI
274
284
  end
275
285
  end
276
286
 
287
+ def documentation_example_variants(kind, auth_method: nil, auth_description: nil, **changes)
288
+ documentation_clients.flat_map do |client|
289
+ context = documentation_context(client, **changes, detail: :simple)
290
+ simple = client.render(kind, context:, auth_method:, auth_description:)
291
+ next [] unless simple
292
+
293
+ levels = client.detail_levels_for(kind, context:, result: simple, auth_method:, auth_description:)
294
+ unless [[:simple], %i[simple thorough]].include?(levels)
295
+ raise ArgumentError, 'Invalid documentation example detail levels'
296
+ end
297
+
298
+ variants = levels.filter_map do |detail|
299
+ result = detail == :simple ? simple : client.render(kind, context: context.with(detail:), auth_method:, auth_description:)
300
+ [client, detail, result] if result
301
+ end
302
+ @documentation_page_details ||= {}
303
+ @documentation_page_details[client.id] = (@documentation_page_details.fetch(client.id, []) | variants.map { |_client, detail, _result| detail })
304
+ variants
305
+ end
306
+ end
307
+
277
308
  def host
278
309
  request.env['HTTP_HOST'].split(':').first
279
310
  end
@@ -299,27 +330,15 @@ module HaveAPI
299
330
  def format_param_type(param)
300
331
  return param[:type] if param[:type] != 'Resource'
301
332
 
302
- "<a href=\"#root-#{param[:resource].join('-')}-show\">#{param[:type]}</a>"
303
- end
333
+ entry = documentation_resources.find { |resource| resource[:path] == param[:resource].map(&:to_s) }
334
+ return param[:type] unless entry
304
335
 
305
- def format_validators(validators)
306
- ret = ''
307
- return ret if validators.nil?
308
-
309
- validators.each do |name, opts|
310
- ret += "<h5>#{escape_html(name.to_s.capitalize)}</h5>"
311
- ret += '<dl>'
312
- if opts.respond_to?(:each_pair)
313
- opts.each_pair do |k, v|
314
- ret += "<dt>#{escape_html(k)}</dt><dd>#{escape_html(v.to_s)}</dd>"
315
- end
316
- else
317
- ret += "<dt>description</dt><dd>#{escape_html(opts.to_s)}</dd>"
318
- end
319
- ret += '</dl>'
320
- end
336
+ anchor = entry[:info][:actions].has_key?('show') || entry[:info][:actions].has_key?(:show) ? "#{entry[:id]}-show" : entry[:id]
337
+ "<a href=\"#{escape_html(documentation_url(resource: entry[:key], anchor:))}\">#{param[:type]}</a>"
338
+ end
321
339
 
322
- ret
340
+ def format_validators(validators, type: nil)
341
+ DocumentationValidators.render(validators, type:)
323
342
  end
324
343
 
325
344
  def escape_html(v)
@@ -342,6 +361,38 @@ module HaveAPI
342
361
  self.locale_header = 'Accept-Language'
343
362
  end
344
363
 
364
+ def documentation_name=(value)
365
+ raise ArgumentError, 'Documentation name must be configured before mount' if @sinatra
366
+
367
+ if value.nil?
368
+ @documentation_name = nil
369
+ return
370
+ end
371
+ unless value.is_a?(String)
372
+ raise ArgumentError, 'Documentation name must be a UTF-8 string'
373
+ end
374
+
375
+ name = value.dup.force_encoding(Encoding::UTF_8)
376
+ unless name.valid_encoding? && !name.match?(/[[:cntrl:]]/)
377
+ raise ArgumentError, 'Documentation name must be a UTF-8 string without control characters'
378
+ end
379
+
380
+ name = name.strip
381
+ unless name.bytesize.between?(1, 256)
382
+ raise ArgumentError, 'Documentation name must contain 1 to 256 bytes'
383
+ end
384
+
385
+ @documentation_name = name.freeze
386
+ end
387
+
388
+ def documentation_oauth(**options)
389
+ return @documentation_oauth if options.empty?
390
+ raise ArgumentError, 'Documentation OAuth is already configured' if @documentation_oauth
391
+ raise ArgumentError, 'Documentation OAuth must be configured before mount' if @sinatra
392
+
393
+ @documentation_oauth = DocumentationOAuth.new(**options)
394
+ end
395
+
345
396
  def documentation_clients(version, **profiles)
346
397
  @documentation_clients.register(version, **profiles)
347
398
  end
@@ -461,6 +512,10 @@ module HaveAPI
461
512
  helpers Sinatra::Cookies
462
513
  helpers ServerHelpers
463
514
  helpers DocHelpers
515
+ helpers DocumentationHelpers
516
+ helpers DocumentationSearch::Helpers
517
+ helpers SharedDocumentationHelpers
518
+ helpers DocumentationOAuth::Helpers
464
519
 
465
520
  before do
466
521
  setup_request_locale
@@ -496,7 +551,15 @@ module HaveAPI
496
551
 
497
552
  # Mount root
498
553
  @sinatra.get @root do
499
- auth_users_by_version = authenticated_versions
554
+ if documentation_selection?
555
+ next render_version_documentation(settings.api_server.default_version)
556
+ end
557
+
558
+ headers 'Cache-Control' => 'private, no-store'
559
+ single_version = settings.api_server.versions.length == 1
560
+ auth_users_by_version = settings.api_server.versions.to_h do |version|
561
+ [version, documentation_authenticated?(version)]
562
+ end
500
563
 
501
564
  @api = settings.api_server.describe(Context.new(
502
565
  settings.api_server,
@@ -505,8 +568,16 @@ module HaveAPI
505
568
  auth_users_by_version:
506
569
  ))
507
570
 
571
+ if single_version
572
+ version = settings.api_server.default_version
573
+ @documentation_index_account = {
574
+ version:, user: auth_users_by_version[version],
575
+ authentication: @api[:versions].fetch(version).fetch(:authentication)
576
+ }
577
+ end
578
+
508
579
  content_type 'text/html'
509
- erb :index, layout: :main_layout
580
+ render_documentation_index
510
581
  end
511
582
 
512
583
  @sinatra.options @root do
@@ -548,67 +619,51 @@ module HaveAPI
548
619
  @formatter.format(true, ret)
549
620
  end
550
621
 
551
- # Doc
622
+ # Shared documentation keeps the existing content and URL aliases.
552
623
  @sinatra.get "#{@root}doc" do
553
- content_type 'text/html'
554
- erb :main_layout do
555
- doc(:index)
556
- end
624
+ render_shared_readme
557
625
  end
558
626
 
559
627
  @sinatra.get "#{@root}doc/readme" do
560
- content_type 'text/html'
561
-
562
- erb :main_layout do
563
- markdown File.new("#{settings.views}/../../../README.md").read
564
- end
628
+ render_shared_readme
565
629
  end
566
630
 
567
631
  @sinatra.get "#{@root}doc/json-schema" do
568
- content_type 'text/html'
569
- erb :doc_layout, layout: :main_layout do
570
- begin
571
- @content = File.read(File.join(settings.root, '../../doc/json-schema.html'))
572
- rescue Errno::ENOENT
573
- halt 404
574
- end
575
-
576
- @sidebar = erb :'doc_sidebars/json-schema'
632
+ begin
633
+ content = File.read(File.join(settings.root, '../../doc/json-schema.html'))
634
+ rescue Errno::ENOENT
635
+ halt 404
577
636
  end
637
+ render_shared_documentation(content:, title: 'JSON schema', sidebar: erb(:'doc_sidebars/json-schema'))
578
638
  end
579
639
 
580
- @sinatra.get %r{#{@root}doc/([^.]+)(\.md)?} do |f, _|
581
- content_type 'text/html'
582
- erb :doc_layout, layout: :main_layout do
583
- begin
584
- @content = doc(f)
585
- rescue Errno::ENOENT
586
- halt 404
587
- end
640
+ @sinatra.get %r{#{@root}doc/([^.]+)(\.md)?} do |file, _|
641
+ next render_shared_readme if file == 'index'
588
642
 
589
- begin
590
- @sidebar = erb :"doc_sidebars/#{f}"
591
- rescue Errno::ENOENT
592
- @sidebar = ''
593
- end
643
+ begin
644
+ content = doc(file)
645
+ rescue Errno::ENOENT
646
+ halt 404
594
647
  end
595
- end
596
-
597
- # Login/logout links
598
- @sinatra.get "#{root}_login" do
599
- if current_user
600
- redirect back
601
- else
602
- authenticate!(settings.api_server.default_version) # FIXME
648
+ sidebar = begin
649
+ erb :"doc_sidebars/#{file}"
650
+ rescue Errno::ENOENT
651
+ nil
603
652
  end
653
+ render_shared_documentation(content:, title: file.humanize, sidebar:)
604
654
  end
605
655
 
606
- @sinatra.get "#{root}_logout" do
607
- require_auth!
656
+ # Retain old documentation links without engaging browser credentials.
657
+ %w[_login _logout].each do |path|
658
+ @sinatra.get "#{root}#{path}" do
659
+ headers 'Cache-Control' => 'private, no-store'
660
+ halt 303, { 'Location' => url(settings.api_server.version_prefix(settings.api_server.default_version), false) }, ''
661
+ end
608
662
  end
609
663
 
610
664
  @auth_chain << HaveAPI.default_authenticate if @auth_chain.empty?
611
665
  @auth_chain.setup(@versions)
666
+ documentation_oauth&.mount(self, @sinatra)
612
667
 
613
668
  @extensions.each { |e| e.enabled(self) }
614
669
 
@@ -618,6 +673,7 @@ module HaveAPI
618
673
  mount_version(@root, @default_version)
619
674
 
620
675
  @versions.each do |v|
676
+ mount_documentation_redirect(v)
621
677
  mount_version(version_prefix(v), v)
622
678
  end
623
679
 
@@ -629,26 +685,28 @@ module HaveAPI
629
685
  raise
630
686
  end
631
687
 
688
+ def mount_documentation_redirect(version)
689
+ prefix = version_prefix(version)
690
+ slashless = prefix.chomp('/')
691
+ @sinatra.get slashless do
692
+ pass unless request.path_info == slashless
693
+
694
+ headers 'Cache-Control' => 'private, no-store'
695
+ query = request.query_string
696
+ halt 400, 'Invalid documentation query' if query.bytes.any? { |byte| byte < 32 || byte == 127 }
697
+ destination = url(prefix, false)
698
+ destination += "?#{query}" unless query.empty?
699
+ halt 308, { 'Location' => destination }, ''
700
+ end
701
+ end
702
+ private :mount_documentation_redirect
703
+
632
704
  def mount_version(prefix, v)
633
705
  @routes[v] ||= {}
634
706
  @routes[v][:resources] = {}
635
707
 
636
708
  @sinatra.get prefix do
637
- authenticated?(v)
638
-
639
- @v = v
640
- @help = settings.api_server.describe_version(Context.new(
641
- settings.api_server,
642
- version: v,
643
- user: current_user,
644
- params:
645
- ))
646
-
647
- content_type 'text/html'
648
- erb :doc_layout, layout: :main_layout do
649
- @content = erb :version_page
650
- @sidebar = erb :version_sidebar
651
- end
709
+ render_version_documentation(v)
652
710
  end
653
711
 
654
712
  @sinatra.options prefix do
@@ -0,0 +1,54 @@
1
+ module HaveAPI
2
+ module SharedDocumentationHelpers
3
+ def render_documentation_index
4
+ render_neutral_documentation(content: erb(:index), title: 'API versions', page: 'versions')
5
+ end
6
+
7
+ def render_shared_documentation(content:, title:, sidebar: nil)
8
+ render_neutral_documentation(content:, title:, sidebar:, page: 'guide')
9
+ end
10
+
11
+ def render_shared_readme
12
+ content = markdown(File.read("#{settings.views}/../../../README.md"),
13
+ with_toc_data: true, tables: true, fenced_code_blocks: true, lax_spacing: true)
14
+ render_shared_documentation(content:, title: 'HaveAPI internals')
15
+ end
16
+
17
+ def shared_documentation_url(path = '')
18
+ url("#{root}#{path}", false)
19
+ end
20
+
21
+ def shared_documentation_reference_url(page: nil)
22
+ version = settings.api_server.default_version
23
+ return if version.nil?
24
+
25
+ path = settings.api_server.version_prefix(version)
26
+ path += "?page=#{CGI.escape(page)}" if page
27
+ url(path, false)
28
+ end
29
+
30
+ private
31
+
32
+ def render_neutral_documentation(content:, title:, page:, sidebar: nil)
33
+ headers 'Cache-Control' => 'private, no-store'
34
+ @documentation_ui = true
35
+ @documentation_page = page
36
+ @documentation_title = title
37
+ @documentation_resource = nil
38
+ @v = nil
39
+ @documentation_toc = sidebar
40
+ content_type 'text/html'
41
+ erb :doc_layout, layout: :main_layout do
42
+ @content = erb :shared_documentation, locals: { content: shared_documentation_tables(content), title: }
43
+ @sidebar = erb :shared_sidebar
44
+ end
45
+ end
46
+
47
+ def shared_documentation_tables(content)
48
+ # Trusted repository HTML is rendered before this presentation-only pass.
49
+ content.gsub(%r{<table\b[^>]*>.*?</table>}m) do |table|
50
+ %(<div class="docs-table-scroll" role="region" aria-label="Documentation table" tabindex="0">#{table}</div>)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,4 +1,4 @@
1
1
  module HaveAPI
2
2
  PROTOCOL_VERSION = '2.0'.freeze
3
- VERSION = '0.31.1'.freeze
3
+ VERSION = '0.32.0'.freeze
4
4
  end
@@ -1,4 +1,18 @@
1
1
  <% yield %>
2
+ <% if @documentation_ui %>
3
+ <div data-docs data-page="<%= @documentation_page %>" data-resource="<%= escape_html(@documentation_resource && @documentation_resource[:key]) %>">
4
+ <a class="docs-skip-link" href="#docs-main">Skip to content</a>
5
+ <div class="docs-mobile-toolbar">
6
+ <button type="button" data-nav-toggle aria-controls="docs-navigation" aria-expanded="false">Navigation</button>
7
+ </div>
8
+ <div class="docs-layout">
9
+ <aside class="docs-sidebar" id="docs-navigation" data-docs-sidebar><%= @sidebar %></aside>
10
+ <main class="docs-main" id="docs-main" tabindex="-1"><%= @content %></main>
11
+ </div>
12
+ <script type="application/json" data-documentation-clients><%= documentation_json(@help ? documentation_client_metadata : []) %></script>
13
+ <script type="application/json" data-legacy-anchors><%= documentation_json(@documentation_page == 'overview' ? documentation_legacy_anchors : {}) %></script>
14
+ </div>
15
+ <% else %>
2
16
  <div class="row-fluid">
3
17
  <div class="col-sm-10">
4
18
  <%= @content %>
@@ -25,3 +39,5 @@
25
39
  });
26
40
  });
27
41
  </script>
42
+
43
+ <% end %>
@@ -1,4 +1,4 @@
1
- <h1>Contents</h1>
1
+ <h2>Contents</h2>
2
2
  <ul class="top-level">
3
3
  <li><a href="#client-definition">Client definition</a></li>
4
4
  <li><a href="#design-rules">Design rules</a></li>
@@ -6,7 +6,7 @@
6
6
  <a href="#necessary-features-to-implement">Necessary features to implement</a>
7
7
  <ul>
8
8
  <li><a href="#resource-tree">Resource tree</a></li>
9
- <li><a href="#inputoutput-parameters">Input/output parameters</a></li>
9
+ <li><a href="#input-output-parameters">Input/output parameters</a></li>
10
10
  <li><a href="#authentication">Authentication</a></li>
11
11
  <li><a href="#object-like-access">Object-like access</a></li>
12
12
  </ul>
@@ -14,7 +14,7 @@
14
14
  <li>
15
15
  <a href="#supplemental-features">Supplemental features</a>
16
16
  <ul>
17
- <li><a href="#client-sde-validations">Client-side validations</a></li>
17
+ <li><a href="#client-side-validations">Client-side validations</a></li>
18
18
  </ul>
19
19
  </li>
20
20
  </ul>
@@ -1,4 +1,4 @@
1
- <h1>Contents</h1>
1
+ <h2>Contents</h2>
2
2
  <ul class="top-level">
3
3
  <li><a href="#envelope">Envelope</a></li>
4
4
  <li><a href="#all">OPTIONS /</a></li>
@@ -1,4 +1,4 @@
1
- <h1>Contents</h1>
1
+ <h2>Contents</h2>
2
2
  <ul class="top-level">
3
3
  <li><a href="#protocol-definition">Protocol definition</a></li>
4
4
  <li><a href="#self-description">Self-description</a></li>
@@ -6,9 +6,9 @@
6
6
  <li>
7
7
  <a href="#description-format">Description format</a>
8
8
  <ul>
9
- <li><a href="#version">Version</a></li>
9
+ <li><a href="#api-version">Version</a></li>
10
10
  <li>
11
- <a href="#Authentication">Authentication</a>
11
+ <a href="#authentication">Authentication</a>
12
12
  <ul>
13
13
  <li><a href="#http-basic-authentication">HTTP basic authentication</a></li>
14
14
  <li><a href="#token-authentication">Token authentication</a></li>
@@ -26,7 +26,7 @@
26
26
  <a href="#parameters">Parameters</a>
27
27
  <ul>
28
28
  <li><a href="#data-types">Data types</a></li>
29
- <li><a href="#resource-associations">Resource associations</a></li>
29
+ <li><a href="#resource-association">Resource associations</a></li>
30
30
  </ul>
31
31
  </li>
32
32
  <li><a href="#examples">Examples</a></li>
@@ -36,7 +36,7 @@
36
36
  </ul>
37
37
  </li>
38
38
  <li><a href="#authorization">Authorization</a></li>
39
- <li><a href="#io">Input/output formats</a></li>
39
+ <li><a href="#input-output-formats">Input/output formats</a></li>
40
40
  <li><a href="#request">Request</a></li>
41
41
  <li><a href="#response">Response</a></li>
42
42
  </ul>
@@ -0,0 +1,10 @@
1
+ <form data-documentation-search method="get" action="<%= escape_html(action) %>" data-search-version="<%= escape_html(version) %>" data-search-index-url="<%= escape_html(index_url) %>" data-search-view-key="<%= escape_html(view_key) %>">
2
+ <input type="hidden" name="page" value="resources">
3
+ <label for="<%= escape_html(id) %>-query">Search API v<%= escape_html(version) %></label>
4
+ <input type="search" name="q" data-search-query id="<%= escape_html(id) %>-query" value="<%= escape_html(query) %>" placeholder="Resource..." maxlength="256">
5
+ <button type="submit">Search</button>
6
+ <div data-search-suggestions role="region" aria-label="Search suggestions" hidden>
7
+ <p data-search-status role="status"></p>
8
+ <div data-search-preview></div>
9
+ </div>
10
+ </form>
@@ -0,0 +1 @@
1
+ <script type="application/json" data-documentation-search-index><%= documentation_json(key: search_index.key, index: search_index.payload) %></script>
@@ -0,0 +1,20 @@
1
+ <section data-search-results aria-label="Search results">
2
+ <h2>Search results for <%= escape_html(search[:query]) %></h2>
3
+ <p data-search-status role="status">
4
+ <% if search[:results].empty? %>
5
+ No matches. Try another resource, action or parameter name.
6
+ <% elsif search[:more] %>
7
+ Showing <%= search[:results].length %> results. More matches are available; refine your search.
8
+ <% else %>
9
+ <%= search[:results].length %> <%= search[:results].length == 1 ? 'result' : 'results' %>.
10
+ <% end %>
11
+ </p>
12
+ <ol>
13
+ <% search[:results].each do |result| %>
14
+ <li>
15
+ <a href="<%= escape_html(documentation_search_result_url(result)) %>"><%= escape_html(result[:kind].to_s.capitalize) %>: <code><%= escape_html(documentation_search_result_title(result)) %></code></a>
16
+ <% documentation_search_result_context(result).each do |context| %><span><%= escape_html(context) %></span><% end %>
17
+ </li>
18
+ <% end %>
19
+ </ol>
20
+ </section>
@@ -1,12 +1,10 @@
1
- <h1>API description</h1>
2
- <h2>Available versions:</h2>
1
+ <h1>API versions</h1>
3
2
  <ul>
4
3
  <% @api[:versions].each do |v, info| %>
5
4
  <% next if v == :default %>
6
5
  <li>
7
- <a href="<%= info[:help] %>">Version <%= v.to_s %></a>
6
+ <a href="<%= escape_html(url(info[:help], false)) %>">Version <%= escape_html(v) %></a>
8
7
  <%= '(default)' if v == @api[:default_version] %>
9
8
  </li>
10
9
  <% end %>
11
10
  </ul>
12
- <h2><a href="/doc">Documentation</a></h2>
@@ -2,7 +2,12 @@
2
2
  <html>
3
3
  <head>
4
4
  <meta charset="utf-8">
5
- <title>API v<%= @v %> description</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title><%= escape_html(documentation_document_title) %></title>
7
+ <% if @documentation_ui %>
8
+ <script src="<%= escape_html(url('/js/documentation.js', false)) %>" data-highlighter-url="<%= escape_html(url('/js/highlighter.js', false)) %>"></script>
9
+ <link rel="stylesheet" href="<%= escape_html(url('/css/documentation.css', false)) %>">
10
+ <% else %>
6
11
 
7
12
  <link rel="stylesheet" href="/css/bootstrap.min.css">
8
13
  <script src="/js/jquery-1.11.1.min.js"></script>
@@ -73,16 +78,17 @@
73
78
  white-space: pre;
74
79
  }
75
80
  </style>
81
+ <% end %>
76
82
  </head>
77
- <body data-spy="scroll" data-target=".table-of-contents">
78
- <div class="container-fluid">
83
+ <body<% unless @documentation_ui %> data-spy="scroll" data-target=".table-of-contents"<% end %>>
84
+ <div class="<%= @documentation_ui ? 'docs-container' : 'container-fluid' %>">
79
85
  <%= yield %>
80
86
 
81
- <div class="row-fluid">
82
- <div class="col-xs-12">
87
+ <footer class="docs-footer">
88
+ <div>
83
89
  <p class="text-right">Generated by <a href="https://github.com/vpsfreecz/haveapi">HaveAPI</a> v<%= version %></p>
84
90
  </div>
85
- </div>
91
+ </footer>
86
92
  </div>
87
93
  </body>
88
94
  </html>
@@ -0,0 +1,12 @@
1
+ <% if @documentation_page == 'guide' %>
2
+ <nav aria-label="Breadcrumb">
3
+ <ol>
4
+ <li><a href="<%= escape_html(shared_documentation_url) %>">API versions</a></li>
5
+ <% unless title == 'HaveAPI internals' %>
6
+ <li><a href="<%= escape_html(shared_documentation_url('doc')) %>">HaveAPI internals</a></li>
7
+ <% end %>
8
+ <li aria-current="page"><%= escape_html(title) %></li>
9
+ </ol>
10
+ </nav>
11
+ <% end %>
12
+ <%= content %>
@@ -0,0 +1,25 @@
1
+ <%= erb :'version_sidebar/brand', locals: {account: documentation_account_context} %>
2
+ <% if (search_options = documentation_search_form_options(id: 'docs-search-sidebar')) %>
3
+ <%= erb :'documentation/search_form', locals: search_options %>
4
+ <% end %>
5
+ <nav aria-label="Documentation pages">
6
+ <ul>
7
+ <li><a href="<%= escape_html(shared_documentation_url) %>"<% if @documentation_page == 'versions' %> aria-current="page"<% end %>>API versions</a></li>
8
+ <% if (reference = shared_documentation_reference_url) %>
9
+ <li><a href="<%= escape_html(reference) %>">API reference</a></li>
10
+ <li><a href="<%= escape_html(shared_documentation_reference_url(page: 'resources')) %>">Resources</a></li>
11
+ <% end %>
12
+ </ul>
13
+ </nav>
14
+ <% if @documentation_toc && !@documentation_toc.empty? %>
15
+ <nav aria-label="Contents" data-action-nav><%= @documentation_toc %></nav>
16
+ <% end %>
17
+ <nav aria-label="Shared documentation">
18
+ <h2><a href="<%= escape_html(shared_documentation_url('doc')) %>">HaveAPI internals</a></h2>
19
+ <ul>
20
+ <li><a href="<%= escape_html(shared_documentation_url('doc/protocol')) %>">Protocol</a></li>
21
+ <li><a href="<%= escape_html(shared_documentation_url('doc/json-schema')) %>">JSON schema</a></li>
22
+ <li><a href="<%= escape_html(shared_documentation_url('doc/create-client')) %>">Create a client</a></li>
23
+ <li><a href="<%= escape_html(shared_documentation_url('doc/client-examples')) %>">Client examples</a></li>
24
+ </ul>
25
+ </nav>