grape-swagger 0.10.2 → 0.10.4

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: 5a6996205661a00c64c62bb85fe5c68ea79c0acf
4
- data.tar.gz: 446c20aa4754161e0fee6aaff5894af24ec06f76
3
+ metadata.gz: 4c3b14815684b5e4e77c01b12a1c64029b4cc367
4
+ data.tar.gz: ecd30bdba3542f6a776c7f94683aab2a2eb32989
5
5
  SHA512:
6
- metadata.gz: 4968dde338ad1282e6100ec9d7b318c76267346f9048d01d8027065fa7e1e7468ff80038448ebf5184d809512c9b8dbaa5584f7996085b63ed76b5092a7de45c
7
- data.tar.gz: 468c21cc9341a54105c7c892432b9e2818b4811bb3add16047b847cac8d6be45beaddfa8b6b41bd08087333b46f1c90317583cf43c3b276cdec1f4b185fdec54
6
+ metadata.gz: 5b2baa995bd32ba699ea6ffc21fb3816b5a6742a2b3be229df6a47e3cdf85622a52468899c1e5cee3b7f77973fe5dcedfa1330b390a981d09ebca7a47b6bdfbd
7
+ data.tar.gz: 96a44e9923a0921f2f368f6f128681fa40796c3987fc46a7de54aec2599d00f0de33b42db4f19b5062857155fbf7395f50c25c95f112f43c86db0a525b9e0920
data/.rubocop_todo.yml CHANGED
@@ -1,55 +1,76 @@
1
- # This configuration was generated by `rubocop --auto-gen-config`
2
- # on 2015-04-01 08:13:45 -0400 using RuboCop version 0.27.0.
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2015-08-19 12:17:53 -0400 using RuboCop version 0.33.0.
3
4
  # The point is for the user to remove these configuration records
4
5
  # one by one as the offenses are removed from the code base.
5
6
  # Note that changes in the inspected code, or installation of new
6
7
  # versions of RuboCop, may require this file to be generated again.
7
8
 
8
- # Offense count: 11
9
+ # Offense count: 10
9
10
  Metrics/AbcSize:
10
- Max: 360
11
+ Max: 214
11
12
 
12
13
  # Offense count: 1
13
14
  # Configuration parameters: CountComments.
14
15
  Metrics/ClassLength:
15
- Max: 509
16
+ Max: 147
16
17
 
17
18
  # Offense count: 6
18
19
  Metrics/CyclomaticComplexity:
19
- Max: 102
20
+ Max: 39
20
21
 
21
- # Offense count: 312
22
+ # Offense count: 304
22
23
  # Configuration parameters: AllowURI, URISchemes.
23
24
  Metrics/LineLength:
24
- Max: 254
25
+ Max: 242
25
26
 
26
27
  # Offense count: 21
27
28
  # Configuration parameters: CountComments.
28
29
  Metrics/MethodLength:
29
- Max: 381
30
+ Max: 170
30
31
 
31
- # Offense count: 5
32
+ # Offense count: 1
33
+ # Configuration parameters: CountComments.
34
+ Metrics/ModuleLength:
35
+ Max: 470
36
+
37
+ # Offense count: 4
32
38
  Metrics/PerceivedComplexity:
33
- Max: 105
39
+ Max: 41
34
40
 
35
41
  # Offense count: 8
36
42
  Style/ClassVars:
37
- Enabled: false
43
+ Exclude:
44
+ - 'example/api.rb'
45
+ - 'lib/grape-swagger/doc_methods.rb'
38
46
 
39
- # Offense count: 81
47
+ # Offense count: 90
40
48
  Style/Documentation:
41
49
  Enabled: false
42
50
 
43
51
  # Offense count: 2
44
52
  Style/DoubleNegation:
45
- Enabled: false
53
+ Exclude:
54
+ - 'lib/grape-swagger/doc_methods.rb'
46
55
 
47
56
  # Offense count: 3
48
57
  # Configuration parameters: Exclude.
49
58
  Style/FileName:
50
- Enabled: false
59
+ Exclude:
60
+ - 'lib/grape-swagger.rb'
61
+ - 'spec/grape-swagger_helper_spec.rb'
62
+ - 'spec/grape-swagger_spec.rb'
51
63
 
52
64
  # Offense count: 1
53
65
  # Configuration parameters: NamePrefix, NamePrefixBlacklist.
54
66
  Style/PredicateName:
55
- Enabled: false
67
+ Exclude:
68
+ - 'lib/grape-swagger/doc_methods.rb'
69
+
70
+ # Offense count: 4
71
+ # Cop supports --auto-correct.
72
+ # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
73
+ Style/RegexpLiteral:
74
+ Exclude:
75
+ - 'lib/grape-swagger.rb'
76
+ - 'lib/grape-swagger/doc_methods.rb'
data/.travis.yml CHANGED
@@ -17,4 +17,5 @@ env:
17
17
  - GRAPE_VERSION=0.11.0
18
18
  - GRAPE_VERSION=0.12.0
19
19
  - GRAPE_VERSION=0.13.0
20
+ - GRAPE_VERSION=0.14.0
20
21
  - GRAPE_VERSION=HEAD
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ### 0.10.4 (December 7, 2015)
2
+
3
+ * [#315](https://github.com/ruby-grape/grape-swagger/pull/315): Require `grape-entity` < 0.5.0 - [@dblock](https://github.com/dblock).
4
+
5
+ ### 0.10.3 (December 7, 2015)
6
+
7
+ * [#292](https://github.com/ruby-grape/grape-swagger/pull/292): Support i18n - [@calfzhou](https://github.com/calfzhou).
8
+ * [#297](https://github.com/ruby-grape/grape-swagger/pull/297): Correct use of documentation param_type - [@fab-girard](https://github.com/fab-girard).
9
+ * [#305](https://github.com/ruby-grape/grape-swagger/pull/305): Speedup by parsing models smarter, not harder - [@jhollinger](https://github.com/jhollinger).
10
+
1
11
  ### 0.10.2 (August 19, 2015)
2
12
 
3
13
  #### Features
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/grape-swagger.svg)](http://badge.fury.io/rb/grape-swagger)
4
4
  [![Build Status](https://travis-ci.org/ruby-grape/grape-swagger.svg?branch=master)](https://travis-ci.org/ruby-grape/grape-swagger)
5
+ [![Dependency Status](https://gemnasium.com/ruby-grape/grape-swagger.svg)](https://gemnasium.com/ruby-grape/grape-swagger)
5
6
  [![Code Climate](https://codeclimate.com/github/ruby-grape/grape-swagger.svg)](https://codeclimate.com/github/ruby-grape/grape-swagger)
6
7
 
7
8
  ## What is grape-swagger?
@@ -92,6 +93,10 @@ API class name.
92
93
 
93
94
  Allow markdown in `detail`/`notes`, default is `nil`. (disabled) See below for details.
94
95
 
96
+ #### i18n_scope
97
+
98
+ Translations scope (or array of scopes) default is `:api`. [See below for details](#i18n).
99
+
95
100
  #### hide_format
96
101
 
97
102
  Don't add `.(format)` to the end of URLs, default is `false`.
@@ -305,7 +310,7 @@ Grape uses the option `default` to set a default value for optional parameters.
305
310
 
306
311
  ## Grape Entities
307
312
 
308
- Add the [grape-entity](https://github.com/agileanimal/grape-entity) gem to our Gemfile.
313
+ Add the [grape-entity](https://github.com/ruby-grape/grape-entity) gem to our Gemfile.
309
314
 
310
315
  The following example exposes statuses. And exposes statuses documentation adding :type and :desc.
311
316
 
@@ -511,6 +516,195 @@ get '/', http_codes: [
511
516
  end
512
517
  ```
513
518
 
519
+ ## I18n
520
+
521
+ Grape-swagger supports I18n for most messages of the JSON documentation, including:
522
+
523
+ * API [info](#info)
524
+ * Namespaces/resources description
525
+ * Endpoints description and detail/notes
526
+ * Params (including headers) description
527
+ * Models/entities description
528
+
529
+ By default, grape-swagger will lookup translations inside `:api` scope. This can be configured by
530
+ set [`i18n_scope`](#i18n_scope) to a custom scope (or an array of scopes) when calling
531
+ `add_swagger_documentation`.
532
+
533
+ ### Translation and Default Message
534
+
535
+ To localize your API document, you can add a locale file containing translated messages.
536
+ Grape-swagger will try to look up translation for each message. If a translation cannot be found,
537
+ it will use the message specified in the API code, then default to blank.
538
+
539
+ Take the following sample API for example:
540
+
541
+ ```ruby
542
+ desc 'Gets all kittens' do
543
+ detail 'This will expose all the kittens.'
544
+ end
545
+ params do
546
+ optional :sort
547
+ end
548
+ get :kittens do
549
+ end
550
+ ```
551
+
552
+ To generate I18n documentation, you can add a locale file with translated messages:
553
+
554
+ ```yaml
555
+ api:
556
+ kittens:
557
+ get:
558
+ detail: This will return a full list of kittens, and you can change the way of sorting them.
559
+ params:
560
+ sort: Specifies the order of results
561
+ ```
562
+
563
+ The endpoint description in generated API document will be "Gets all kittens" - specified in source
564
+ code - because there is no translation defined. And endpoint details will be overwritten by locale
565
+ message - "This will return a full list of kittens, and you can change the way of sorting them.".
566
+ Parameter `:sort` will have a description from locale file too.
567
+
568
+ ### Default Lookup Keys
569
+
570
+ The following lookup keys are all within the scope given by `i18n_scope`.
571
+
572
+ #### API Info
573
+
574
+ The default lookup key of a field in API info is `info.<field>`, i.e.:
575
+
576
+ * `info.title` for `:title` field.
577
+ * `info.desc` or `info.description` for `:description` field.
578
+ * `info.contact` for `:contact` field.
579
+ * `info.license` for `:license` field.
580
+ * `info.license_url` for `:license_url` field.
581
+ * `info.terms_of_service_url` for `:terms_of_service_url` field.
582
+
583
+ #### Namespaces/Resources Description
584
+
585
+ Grape-swagger will give each root namespace a default description, such as "*Operations about
586
+ users*", where `users` is a namespace (pluralized). But you can change it by provide your own
587
+ message under key `<namespace>.desc` or `<namespace>.description` in your locale file. And the
588
+ namespace itself is available as a variable for interpolation.
589
+
590
+ ### Endpoints Description and Detail/Notes
591
+
592
+ An endpoint's default lookup key is in format of `<namespace>.<path>.<http_method>`. And if there
593
+ are nested namespaces or multi-level path, all parts will be join by `.`. For example for this
594
+ endpoint:
595
+
596
+ ```ruby
597
+ namespace :users do
598
+ route_params :id do
599
+ get :'password/strength' do
600
+ end
601
+ end
602
+ end
603
+ ```
604
+
605
+ Its corresponding lookup key will be `users.:id.password.strength.get`, let's say that this is
606
+ the key of this endpoint.
607
+
608
+ So an endpoint's description message can be given under `<endpoint_key>.desc` or
609
+ `<endpoint_key>.description`. And use `<endpoint_key>.detail` or `<endpoint_key>.notes` for the
610
+ message of its further details.
611
+
612
+ #### Params Description
613
+
614
+ The first default key of endpoint parameter's description translation is
615
+ `<endpoint_key>.params.<param_name>`. But considering that some endpoints could usually share a
616
+ same parameter, it will be a little annoyed to duplicate its description message everywhere. So if
617
+ no translation found in the first default key, grape-swagger will try to find all its parent keys.
618
+ Take above endpoint for example, it may have a parameter named `:id`, then the lookup keys are:
619
+
620
+ 1. `users.:id.password.strength.get.params.id`
621
+ 1. `users.:id.password.strength.params.id`
622
+ 1. `users.:id.password.params.id`
623
+ 1. `users.:id.params.id`
624
+ 1. `users.params.id`
625
+ 1. `params.id`
626
+
627
+ #### Models/Entities Description
628
+
629
+ A model class' lookup key is by default its class name, without module part, and underscored. Each
630
+ property's key is then `entities.<class_key>.<property_name>`. When not found, grape-swagger will
631
+ try to check the its ancestors, up to a class whose name is `Entity`.
632
+
633
+ Say if there is model class `User` inherits `Grape::Entity`, and another model `AdminUser` inherits
634
+ `User`, to translate a property named `:email` of `AdminUser`, the lookup keys are:
635
+
636
+ 1. `entities.admin_user.email`
637
+ 1. `entities.user.email`
638
+ 1. `entities.default.email`
639
+
640
+ #### Example Locale File
641
+
642
+ ```yaml
643
+ en:
644
+ api:
645
+ info:
646
+ title: My Awesome API
647
+ desc: Some detail information about this API.
648
+ entities:
649
+ default:
650
+ id: Resource identifier
651
+ user:
652
+ name: User's real name
653
+ email: User's login email address
654
+ sign_up_at: When the user signed up
655
+ admin_user:
656
+ level: Which level the admin is
657
+ password_strength:
658
+ level: A 0~4 integer indicates `very_weak` to `strong`
659
+ crack_time: An estimated time for force cracking the password, in seconds
660
+ params:
661
+ locale: Used to change locale of endpoint's responding message
662
+ sort: To specify the order of result list, default is %{default_sort}
663
+ users:
664
+ desc: Operations about not-disabled users
665
+ get:
666
+ desc: Gets a list of users
667
+ detail: You can control how to sort the results.
668
+ ':id':
669
+ params:
670
+ id: User id
671
+ get:
672
+ desc: Finds user by id
673
+ email:
674
+ put:
675
+ desc: Changes a user's email
676
+ params:
677
+ email: A new email
678
+ password:
679
+ strength:
680
+ get:
681
+ desc: Gets the strength estimation of a user's password
682
+ detail: The estimation is done by a well-known algorithm when he changed his password
683
+ swagger_doc:
684
+ desc: Endpoints for API documents
685
+ get:
686
+ desc: Gets root API document
687
+ ':name':
688
+ get:
689
+ desc: Gets specific resource API document
690
+ params:
691
+ name: Resource name
692
+ ```
693
+
694
+ ### Customization
695
+
696
+ The translation can be customized by using different kind/format of message in source code.
697
+
698
+ * A string or `nil`: It will become the default message when a translation is not defined.
699
+ * A symbol: Looks up translation using the symbol as a key, but will fallback to default key(s).
700
+ * A hash with the following keys:
701
+ * key: A symbol, defines custom lookup key.
702
+ * default: A string, defines the default message when translation can not be found.
703
+ * translate: A boolean (default is `true`), set to `false` to skip translation for this message.
704
+ * scope: A symbol, or a string, or an array of them. Used to replace the `i18n_scope` config
705
+ value for a custom lookup scope.
706
+ * Any other key/value will be sent to the translation function for interpolation.
707
+
514
708
  ## Contributing to grape-swagger
515
709
 
516
710
  See [CONTRIBUTING](CONTRIBUTING.md).
data/example/config.ru CHANGED
@@ -2,7 +2,7 @@ require 'rack/cors'
2
2
  use Rack::Cors do
3
3
  allow do
4
4
  origins '*'
5
- resource '*', headers: :any, methods: [ :get, :post, :put, :delete, :options ]
5
+ resource '*', headers: :any, methods: [:get, :post, :put, :delete, :options]
6
6
  end
7
7
  end
8
8
 
@@ -12,7 +12,7 @@ Gem::Specification.new do |s|
12
12
  s.license = 'MIT'
13
13
 
14
14
  s.add_runtime_dependency 'grape', '>= 0.8.0'
15
- s.add_runtime_dependency 'grape-entity'
15
+ s.add_runtime_dependency 'grape-entity', '< 0.5.0'
16
16
 
17
17
  s.add_development_dependency 'rake'
18
18
  s.add_development_dependency 'shoulda'
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.add_development_dependency 'bundler'
22
22
  s.add_development_dependency 'rack-test'
23
23
  s.add_development_dependency 'rack-cors'
24
- s.add_development_dependency 'rubocop', '0.27.0'
24
+ s.add_development_dependency 'rubocop', '0.33.0'
25
25
  s.add_development_dependency 'kramdown', '~> 1.4.1'
26
26
  s.add_development_dependency 'redcarpet', '~> 3.1.2' unless RUBY_PLATFORM.eql? 'java'
27
27
  s.add_development_dependency 'rouge', '~> 1.6.1'
data/lib/grape-swagger.rb CHANGED
@@ -10,6 +10,7 @@ module Grape
10
10
  class API
11
11
  class << self
12
12
  attr_accessor :combined_routes, :combined_namespaces, :combined_namespace_routes, :combined_namespace_identifiers
13
+ attr_accessor :endpoint_mapping
13
14
 
14
15
  def add_swagger_documentation(options = {})
15
16
  documentation_class = create_documentation_class
@@ -35,6 +36,7 @@ module Grape
35
36
  @target_class.combined_routes[resource] << route
36
37
  end
37
38
 
39
+ @target_class.endpoint_mapping = {}
38
40
  @target_class.combined_namespaces = {}
39
41
  combine_namespaces(@target_class)
40
42
 
@@ -60,6 +62,10 @@ module Grape
60
62
  # and strip leading slash
61
63
  @target_class.combined_namespaces[endpoint.namespace.sub(/^\//, '')] = ns if ns
62
64
 
65
+ endpoint.routes.each do |route|
66
+ @target_class.endpoint_mapping[route.to_s.sub('(.:format)', '')] = endpoint
67
+ end
68
+
63
69
  combine_namespaces(endpoint.options[:app]) if endpoint.options[:app]
64
70
  end
65
71
  end
@@ -79,9 +85,9 @@ module Grape
79
85
  if namespace.options.key?(:swagger) && namespace.options[:swagger][:nested] == false
80
86
  # Namespace shall appear as standalone resource, use specified name or use normalized path as name
81
87
  if namespace.options[:swagger].key?(:name)
82
- identifier = namespace.options[:swagger][:name].gsub(/ /, '-')
88
+ identifier = namespace.options[:swagger][:name].tr(' ', '-')
83
89
  else
84
- identifier = name.gsub(/_/, '-').gsub(/\//, '_')
90
+ identifier = name.tr('_', '-').gsub(/\//, '_')
85
91
  end
86
92
  @target_class.combined_namespace_identifiers[identifier] = name
87
93
  @target_class.combined_namespace_routes[identifier] = namespace_routes
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  module GrapeSwagger
2
4
  module DocMethods
3
5
  PRIMITIVE_MAPPINGS = {
@@ -14,11 +16,43 @@ module GrapeSwagger
14
16
  @@class_name
15
17
  end
16
18
 
19
+ def translate(message, scope, default, params = {})
20
+ if message.is_a?(String)
21
+ text = message
22
+ elsif message.is_a?(Symbol)
23
+ key = message
24
+ elsif message.is_a?(Hash)
25
+ message = message.dup
26
+ key = message.delete(:key)
27
+ text = message.delete(:default)
28
+ skip_translate = !message.delete(:translate) if message.key?(:translate)
29
+ scope = message.delete(:scope) if message.key?(:scope)
30
+ params = params.merge(message) unless message.empty?
31
+ end
32
+
33
+ return text if skip_translate
34
+
35
+ default = Array(default).dup << (text || '')
36
+ I18n.t(key, params.merge(scope: scope, default: default))
37
+ end
38
+
39
+ def expand_scope(scope)
40
+ scopes = []
41
+ scope = scope.to_s
42
+ until scope.blank?
43
+ scopes << scope.to_sym
44
+ scope = scope.rpartition('.')[0]
45
+ end
46
+ scopes << :''
47
+ end
48
+
17
49
  def as_markdown(description)
18
50
  description && @@markdown ? @@markdown.as_markdown(strip_heredoc(description)) : description
19
51
  end
20
52
 
21
- def parse_params(params, path, method)
53
+ def parse_params(params, path, method, options = {})
54
+ scope = options[:scope]
55
+ i18n_keys = expand_scope(options[:key])
22
56
  params ||= []
23
57
 
24
58
  parsed_array_params = parse_array_params(params)
@@ -71,27 +105,29 @@ module GrapeSwagger
71
105
  values = value.is_a?(Hash) ? value[:values] : nil
72
106
  enum_or_range_values = parse_enum_or_range_values(values)
73
107
 
74
- if value.is_a?(Hash) && value.key?(:documentation) && value[:documentation].key?(:param_type)
75
- param_type = value[:documentation][:param_type]
108
+ if value.is_a?(Hash) && value.key?(:param_type)
109
+ param_type = value[:param_type]
76
110
  if is_array
77
111
  items = { '$ref' => data_type }
78
112
  data_type = 'array'
79
113
  end
80
114
  else
81
- param_type = case
82
- when path.include?(":#{param}")
83
- 'path'
84
- when %w(POST PUT PATCH).include?(method)
85
- if is_primitive?(data_type)
86
- 'form'
87
- else
88
- 'body'
89
- end
90
- else
91
- 'query'
115
+ param_type = case
116
+ when path.include?(":#{param}")
117
+ 'path'
118
+ when %w(POST PUT PATCH).include?(method)
119
+ if is_primitive?(data_type)
120
+ 'form'
121
+ else
122
+ 'body'
123
+ end
124
+ else
125
+ 'query'
92
126
  end
93
127
  end
94
128
  name = (value.is_a?(Hash) && value[:full_name]) || param
129
+ description = translate(description, scope,
130
+ i18n_keys.map { |key| :"#{key}.params.#{name}" })
95
131
 
96
132
  parsed_params = {
97
133
  paramType: param_type,
@@ -99,14 +135,14 @@ module GrapeSwagger
99
135
  description: as_markdown(description),
100
136
  type: data_type,
101
137
  required: required,
102
- allowMultiple: is_array
138
+ allowMultiple: is_array && data_type != 'array' && %w(query header path).include?(param_type)
103
139
  }
104
140
 
105
141
  if PRIMITIVE_MAPPINGS.key?(data_type)
106
142
  parsed_params[:type], parsed_params[:format] = PRIMITIVE_MAPPINGS[data_type]
107
143
  end
108
144
 
109
- parsed_params[:items] = items if items.present?
145
+ parsed_params[:items] = items if items.present?
110
146
 
111
147
  parsed_params[:defaultValue] = example if example
112
148
  if default_value && example.blank?
@@ -130,18 +166,22 @@ module GrapeSwagger
130
166
  content_types.uniq
131
167
  end
132
168
 
133
- def parse_info(info)
169
+ def parse_info(info, options = {})
170
+ scope = options[:scope]
171
+
134
172
  {
135
- contact: info[:contact],
136
- description: as_markdown(info[:description]),
137
- license: info[:license],
138
- licenseUrl: info[:license_url],
139
- termsOfServiceUrl: info[:terms_of_service_url],
140
- title: info[:title]
173
+ contact: translate(info[:contact], scope, :'info.contact'),
174
+ description: as_markdown(translate(info[:description], scope, [:'info.desc', :'info.description'])),
175
+ license: translate(info[:license], scope, :'info.license'),
176
+ licenseUrl: translate(info[:license_url], scope, :'info.license_url'),
177
+ termsOfServiceUrl: translate(info[:terms_of_service_url], scope, :'info.terms_of_service_url'),
178
+ title: translate(info[:title], scope, :'info.title')
141
179
  }.delete_if { |_, value| value.blank? }
142
180
  end
143
181
 
144
- def parse_header_params(params)
182
+ def parse_header_params(params, options = {})
183
+ scope = options[:scope]
184
+ i18n_keys = expand_scope(options[:key])
145
185
  params ||= []
146
186
 
147
187
  params.map do |param, value|
@@ -151,6 +191,9 @@ module GrapeSwagger
151
191
  default_value = value.is_a?(Hash) ? value[:default] : nil
152
192
  param_type = 'header'
153
193
 
194
+ description = translate(description, scope,
195
+ i18n_keys.map { |key| :"#{key}.params.#{param}" })
196
+
154
197
  parsed_params = {
155
198
  paramType: param_type,
156
199
  name: param,
@@ -191,13 +234,22 @@ module GrapeSwagger
191
234
  end
192
235
  end
193
236
 
194
- def parse_entity_models(models)
237
+ def parse_entity_models(models, options = {})
238
+ scope = options[:scope]
195
239
  result = {}
196
240
  models.each do |model|
197
241
  name = (model.instance_variable_get(:@root) || parse_entity_name(model))
198
242
  properties = {}
199
243
  required = []
200
244
 
245
+ i18n_keys = []
246
+ klass = model
247
+ until %w(entity object).include? klass.name.demodulize.underscore
248
+ i18n_keys << klass.name.demodulize.underscore.to_sym
249
+ klass = klass.superclass
250
+ end
251
+ i18n_keys << :default
252
+
201
253
  model.documentation.each do |property_name, property_info|
202
254
  p = property_info.dup
203
255
 
@@ -219,7 +271,9 @@ module GrapeSwagger
219
271
 
220
272
  # rename Grape Entity's "desc" to "description"
221
273
  property_description = p.delete(:desc)
222
- p[:description] = property_description if property_description
274
+ property_description = translate(property_description, scope,
275
+ i18n_keys.map { |key| :"entities.#{key}.#{property_name}" })
276
+ p[:description] = property_description unless property_description.blank?
223
277
 
224
278
  # rename Grape's 'values' to 'enum'
225
279
  select_values = p.delete(:values)
@@ -324,6 +378,7 @@ module GrapeSwagger
324
378
  base_path: nil,
325
379
  api_version: '0.1',
326
380
  markdown: nil,
381
+ i18n_scope: :api,
327
382
  hide_documentation_path: false,
328
383
  hide_format: false,
329
384
  format: nil,
@@ -339,7 +394,7 @@ module GrapeSwagger
339
394
 
340
395
  target_class = options[:target_class]
341
396
  @@mount_path = options[:mount_path]
342
- @@class_name = options[:class_name] || options[:mount_path].gsub('/', '')
397
+ @@class_name = options[:class_name] || options[:mount_path].delete('/')
343
398
  @@markdown = options[:markdown] ? GrapeSwagger::Markdown.new(options[:markdown]) : nil
344
399
  @@hide_format = options[:hide_format]
345
400
  api_version = options[:api_version]
@@ -349,6 +404,7 @@ module GrapeSwagger
349
404
  api_doc = options[:api_documentation].dup
350
405
  specific_api_doc = options[:specific_api_documentation].dup
351
406
  @@models = options[:models] || []
407
+ i18n_scope = options[:i18n_scope]
352
408
 
353
409
  @@hide_documentation_path = options[:hide_documentation_path]
354
410
 
@@ -361,7 +417,11 @@ module GrapeSwagger
361
417
  @@documentation_class = self
362
418
 
363
419
  desc api_doc.delete(:desc), api_doc
420
+ params do
421
+ optional :locale, type: Symbol, desc: 'Locale of API documentation'
422
+ end
364
423
  get @@mount_path do
424
+ I18n.locale = params[:locale] || I18n.default_locale
365
425
  header['Access-Control-Allow-Origin'] = '*'
366
426
  header['Access-Control-Request-Method'] = '*'
367
427
 
@@ -375,14 +435,23 @@ module GrapeSwagger
375
435
  namespace_routes_array = namespace_routes.keys.map do |local_route|
376
436
  next if namespace_routes[local_route].map(&:route_hidden).all? { |value| value.respond_to?(:call) ? value.call : value }
377
437
 
378
- url_format = '.{format}' unless @@hide_format
438
+ url_format = '.{format}' unless @@hide_format
439
+ url_locale = "?locale=#{params[:locale]}" unless params[:locale].blank?
379
440
 
380
441
  original_namespace_name = target_class.combined_namespace_identifiers.key?(local_route) ? target_class.combined_namespace_identifiers[local_route] : local_route
381
442
  description = namespaces[original_namespace_name] && namespaces[original_namespace_name].options[:desc]
382
443
  description ||= "Operations about #{original_namespace_name.pluralize}"
444
+ description = @@documentation_class.translate(
445
+ description, i18n_scope,
446
+ [
447
+ :"#{original_namespace_name}.desc",
448
+ :"#{original_namespace_name}.description"
449
+ ],
450
+ namespace: original_namespace_name.pluralize
451
+ )
383
452
 
384
453
  {
385
- path: "/#{local_route}#{url_format}",
454
+ path: "/#{local_route}#{url_format}#{url_locale}",
386
455
  description: description
387
456
  }
388
457
  end.compact
@@ -392,7 +461,7 @@ module GrapeSwagger
392
461
  swaggerVersion: '1.2',
393
462
  produces: @@documentation_class.content_types_for(target_class),
394
463
  apis: namespace_routes_array,
395
- info: @@documentation_class.parse_info(extra_info)
464
+ info: @@documentation_class.parse_info(extra_info, scope: i18n_scope)
396
465
  }
397
466
 
398
467
  output[:authorizations] = authorizations unless authorizations.nil? || authorizations.empty?
@@ -403,13 +472,15 @@ module GrapeSwagger
403
472
  desc specific_api_doc.delete(:desc), { params:
404
473
  specific_api_doc.delete(:params) || {} }.merge(specific_api_doc)
405
474
  params do
475
+ optional :locale, type: Symbol, desc: 'Locale of API documentation'
406
476
  requires :name, type: String, desc: 'Resource name of mounted API'
407
477
  end
408
478
  get "#{@@mount_path}/:name" do
479
+ I18n.locale = params[:locale] || I18n.default_locale
409
480
  header['Access-Control-Allow-Origin'] = '*'
410
481
  header['Access-Control-Request-Method'] = '*'
411
482
 
412
- models = []
483
+ models = Set.new(@@models.dup)
413
484
  routes = target_class.combined_namespace_routes[params[:name]]
414
485
  error!('Not Found', 404) unless routes
415
486
 
@@ -427,22 +498,33 @@ module GrapeSwagger
427
498
 
428
499
  ops.each do |path, op_routes|
429
500
  operations = op_routes.map do |route|
430
- notes = @@documentation_class.as_markdown(route.route_detail || route.route_notes)
501
+ endpoint = target_class.endpoint_mapping[route.to_s.sub('(.:format)', '')]
502
+ endpoint_path = endpoint.options[:path] unless endpoint.nil?
503
+ i18n_key = [route.route_namespace, endpoint_path, route.route_method.downcase].flatten.join('/')
504
+ i18n_key = i18n_key.split('/').reject(&:empty?).join('.')
505
+
506
+ summary = @@documentation_class.translate(
507
+ route.route_description, i18n_scope,
508
+ [:"#{i18n_key}.desc", :"#{i18n_key}.description"]
509
+ )
510
+ notes = @@documentation_class.translate(
511
+ route.route_detail || route.route_notes, i18n_scope,
512
+ [:"#{i18n_key}.detail", :"#{i18n_key}.notes"]
513
+ )
514
+ notes = @@documentation_class.as_markdown(notes)
431
515
 
432
516
  http_codes = @@documentation_class.parse_http_codes(route.route_http_codes, models)
433
517
 
434
- models |= @@models if @@models.present?
435
-
436
- models |= Array(route.route_entity) if route.route_entity.present?
437
-
438
- models = @@documentation_class.models_with_included_presenters(models.flatten.compact)
518
+ models.merge(Array(route.route_entity)) if route.route_entity.present?
439
519
 
440
520
  operation = {
441
521
  notes: notes.to_s,
442
- summary: route.route_description || '',
522
+ summary: summary,
443
523
  nickname: route.route_nickname || (route.route_method + route.route_path.gsub(/[\/:\(\)\.]/, '-')),
444
524
  method: route.route_method,
445
- parameters: @@documentation_class.parse_header_params(route.route_headers) + @@documentation_class.parse_params(route.route_params, route.route_path, route.route_method),
525
+ parameters: @@documentation_class.parse_header_params(route.route_headers, scope: i18n_scope, key: i18n_key) +
526
+ @@documentation_class.parse_params(route.route_params, route.route_path, route.route_method,
527
+ scope: i18n_scope, key: i18n_key),
446
528
  type: route.route_is_array ? 'array' : 'void'
447
529
  }
448
530
  operation[:authorizations] = route.route_authorizations unless route.route_authorizations.nil? || route.route_authorizations.empty?
@@ -469,6 +551,8 @@ module GrapeSwagger
469
551
  }
470
552
  end
471
553
 
554
+ models = @@documentation_class.models_with_included_presenters(models.to_a.flatten.compact)
555
+
472
556
  # use custom resource naming if available
473
557
  if target_class.combined_namespace_identifiers.key? params[:name]
474
558
  resource_path = target_class.combined_namespace_identifiers[params[:name]]
@@ -485,7 +569,7 @@ module GrapeSwagger
485
569
 
486
570
  base_path = @@documentation_class.parse_base_path(options[:base_path], request)
487
571
  api_description[:basePath] = base_path if base_path && base_path.size > 0 && root_base_path != false
488
- api_description[:models] = @@documentation_class.parse_entity_models(models) unless models.empty?
572
+ api_description[:models] = @@documentation_class.parse_entity_models(models, scope: i18n_scope) unless models.empty?
489
573
  api_description[:authorizations] = authorizations if authorizations
490
574
 
491
575
  api_description