privatenote_olive_branch 1.0.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +153 -0
- data/lib/olive_branch/middleware.rb +128 -0
- data/lib/olive_branch/version.rb +3 -0
- data/lib/olive_branch.rb +3 -0
- metadata +104 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8b71202e524bfcc0c98fdc1a178ccdfa90c404127a63a2502b2557038f6d0c30
|
4
|
+
data.tar.gz: 13fb35ee7e97a80a635d759d4ea86b38d102292c27239c83507aaef8a24271b2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ee5c41e04b759bef485b4f97ec53004861e7456696d0ae4bc8861378fdc7d739547384e485f6afdc84157c0048462c8462b3cd6202dcac15acaf4bcbfbba58f7
|
7
|
+
data.tar.gz: 2e42b65c14b7c2dc13b490a748351cdfff83e3eb625838f627b52ef029635d757ed7d52e1260f58f3aeb956a0be3c1e7dcb0ff6a789d27f5f89f75bdf32ed4dc
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 Eli Fatsi (Viget)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
# OliveBranch
|
2
|
+
|
3
|
+
[](https://codeclimate.com/github/vigetlabs/olive_branch)
|
4
|
+
[](https://travis-ci.org/vigetlabs/olive_branch)
|
5
|
+
|
6
|
+
This gem lets your API users pass in and receive camelCased or dash-cased keys, while your Rails app receives and produces snake_cased ones.
|
7
|
+
|
8
|
+
## 📣 This repository is forked repo.
|
9
|
+
|
10
|
+
The original repository is no longer actively maintained. (last updated on Nov 19, 2021)
|
11
|
+
|
12
|
+
To prepare for problems that may arise, we decided to fork and use it.
|
13
|
+
|
14
|
+
This forked repository is also under the MIT License same as original repository.
|
15
|
+
|
16
|
+
## Install
|
17
|
+
|
18
|
+
1. Add this to your Gemfile and then `bundle install`:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem "olive_branch"
|
22
|
+
```
|
23
|
+
|
24
|
+
2. Add this to `config/applcation.rb` if you want the clients to control the transformation behaviour through the `Key-Inflection` HTTP header sent by the client:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
config.middleware.use OliveBranch::Middleware
|
28
|
+
```
|
29
|
+
|
30
|
+
Alternative, if you want to always convert between snake_case and camelCase for your API and only your API, to keep Rubyist and JavaScript developer's happy, use the following configuration:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
excluded_routes = ->(env) { !env["PATH_INFO"].match(%r{^/api}) }
|
34
|
+
config.middleware.use OliveBranch::Middleware,
|
35
|
+
inflection: "camel",
|
36
|
+
exclude_params: excluded_routes,
|
37
|
+
exclude_response: excluded_routes
|
38
|
+
```
|
39
|
+
|
40
|
+
in your `config/application.rb`.
|
41
|
+
|
42
|
+
## Use
|
43
|
+
|
44
|
+
Include a `Key-Inflection` header with values of `camel`, `dash`, `snake` or `pascal` in your JSON API requests.
|
45
|
+
|
46
|
+
For more examples, see [our blog post](https://www.viget.com/articles/introducing-olivebranch).
|
47
|
+
|
48
|
+
## Optimizations and configuration
|
49
|
+
|
50
|
+
`OliveBranch` uses `multi_json`, which will automatically choose the fastest available JSON parsing library present in your application.
|
51
|
+
Most Ruby applications default to using the JSON library that ships with Ruby. However, by including a coder that `multi_json` considers faster, like [Oj](https://github.com/ohler55/oj) in your gemfile, you can potentially save up to ~20% response time.
|
52
|
+
|
53
|
+
The middleware can be initialized with custom camelize/dasherize implementations, so if you know you have a fixed size set of keys, you can save a considerable amount of time by providing a custom camelize that caches like so:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class FastCamel
|
57
|
+
def self.camel_cache
|
58
|
+
@camel_cache ||= {}
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.camelize(string)
|
62
|
+
camel_cache[string] ||= string.underscore.camelize(:lower)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
...
|
68
|
+
|
69
|
+
config.middleware.use OliveBranch::Middleware, camelize: FastCamel.method(:camelize)
|
70
|
+
```
|
71
|
+
|
72
|
+
Default inflection header key can be changed like
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
config.middleware.use OliveBranch::Middleware, inflection_header: 'Inflect-With'
|
76
|
+
```
|
77
|
+
|
78
|
+
A default inflection can be specified so you don't have to include the `Key-Inflection` header on every request. If you opt for default inflection, you may want to exclude the routes that Rails uses (see Filtering).
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
config.middleware.use OliveBranch::Middleware, inflection: 'camel'
|
82
|
+
```
|
83
|
+
|
84
|
+
A benchmark of this compared to the standard implementation shows a saving of ~75% rails response times for a complex response payload, or a ~400% improvement, but there is a risk of memory usage ballooning if you have dynamic keys. You can make this method as complex as required, but keep in mind that it will end up being called a _lot_ in a busy app, so it's worth thinking about how to do what you need in the fastest manner possible.
|
85
|
+
|
86
|
+
### Filtering
|
87
|
+
|
88
|
+
#### Content type
|
89
|
+
|
90
|
+
It is also possible to include a custom content type check in the same manner
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
config.middleware.use OliveBranch::Middleware, content_type_check: -> (content_type) {
|
94
|
+
content_type == "my/content-type"
|
95
|
+
}
|
96
|
+
```
|
97
|
+
|
98
|
+
#### Excluding URLs
|
99
|
+
|
100
|
+
Additionally you can define a custom check by passing a proc
|
101
|
+
|
102
|
+
For params transforming
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
config.middleware.use OliveBranch::Middleware, exclude_params: -> (env) {
|
106
|
+
env['PATH_INFO'].match(/^\/do_not_transform/)
|
107
|
+
}
|
108
|
+
```
|
109
|
+
|
110
|
+
Or response transforming
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
config.middleware.use OliveBranch::Middleware, exclude_response: -> (env) {
|
114
|
+
env['PATH_INFO'].match(/^\/do_not_transform/)
|
115
|
+
}
|
116
|
+
```
|
117
|
+
|
118
|
+
#### Rails routes & Action Text
|
119
|
+
|
120
|
+
If you're using default inflection, exclude the routes that Rails uses
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
rails_routes = -> (env) { env['PATH_INFO'].match(/^\/rails/) }
|
124
|
+
config.middleware.use OliveBranch::Middleware, inflection: "camel", exclude_params: rails_routes, exclude_response: rails_routes
|
125
|
+
```
|
126
|
+
|
127
|
+
## Upgrading to version 3
|
128
|
+
|
129
|
+
Default inflection header changed from `X-Key-Inflection` to `Key-Inflection`.
|
130
|
+
|
131
|
+
## Troubleshooting
|
132
|
+
|
133
|
+
We've seen folks raise issues that inbound transformations are not taking place. This is often due to the fact that OliveBranch, by default, is only transforming keys when a request's Content-Type is `application/json`.
|
134
|
+
|
135
|
+
Note that your HTTP client library may suppress even a manually specified `Content-Type` header if the request body is empty (e.g. [Axios does this](https://github.com/axios/axios/issues/86)). This is a common gotcha for GET requests, the body of which are [often expected to be empty](https://stackoverflow.com/questions/978061/http-get-with-request-body) for reasons of caching. If you're seeing the middleware perform on POST or PATCH requests, but not GET requests, this may be your issue.
|
136
|
+
|
137
|
+
You may choose to force inbound transformation on every request by overriding the `content_type_check` functionality:
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
config.middleware.use OliveBranch::Middleware, content_type_check: -> (content_type) { true }
|
141
|
+
```
|
142
|
+
|
143
|
+
---
|
144
|
+
|
145
|
+
OliveBranch is released under the [MIT License](http://www.opensource.org/licenses/MIT). See MIT-LICENSE for further details.
|
146
|
+
|
147
|
+
---
|
148
|
+
|
149
|
+
<a href="http://code.viget.com">
|
150
|
+
<img src="http://code.viget.com/github-banner.png" alt="Code At Viget">
|
151
|
+
</a>
|
152
|
+
|
153
|
+
Visit [code.viget.com](http://code.viget.com) to see more projects from [Viget.](https://viget.com)
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require "multi_json"
|
2
|
+
|
3
|
+
module OliveBranch
|
4
|
+
class Checks
|
5
|
+
def self.content_type_check(content_type)
|
6
|
+
content_type =~ /application\/json/ || content_type =~ /application\/vnd\.api\+json/
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.default_exclude(env)
|
10
|
+
false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Transformations
|
15
|
+
class << self
|
16
|
+
def transform(value, transform_method)
|
17
|
+
case value
|
18
|
+
when Array then value.map { |item| transform(item, transform_method) }
|
19
|
+
when Hash then value.deep_transform_keys! { |key| transform(key, transform_method) }
|
20
|
+
when String then transform_method.call(value)
|
21
|
+
else value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def pascalize(string)
|
26
|
+
string.underscore.camelize(:upper)
|
27
|
+
end
|
28
|
+
|
29
|
+
def camelize(string)
|
30
|
+
string.underscore.camelize(:lower)
|
31
|
+
end
|
32
|
+
|
33
|
+
def dasherize(string)
|
34
|
+
string.dasherize
|
35
|
+
end
|
36
|
+
|
37
|
+
def underscore_params(env)
|
38
|
+
req = ActionDispatch::Request.new(env)
|
39
|
+
req.request_parameters
|
40
|
+
req.query_parameters
|
41
|
+
|
42
|
+
env["action_dispatch.request.request_parameters"].deep_transform_keys!(&:underscore)
|
43
|
+
env["action_dispatch.request.query_parameters"].deep_transform_keys!(&:underscore)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class Middleware
|
49
|
+
def initialize(app, args = {})
|
50
|
+
@app = app
|
51
|
+
@camelize = args[:camelize] || Transformations.method(:camelize)
|
52
|
+
@dasherize = args[:dasherize] || Transformations.method(:dasherize)
|
53
|
+
@pascalize = args[:pascalize] || Transformations.method(:pascalize)
|
54
|
+
@content_type_check = args[:content_type_check] || Checks.method(:content_type_check)
|
55
|
+
@exclude_response = args[:exclude_response] || Checks.method(:default_exclude)
|
56
|
+
@exclude_params = args[:exclude_params] || Checks.method(:default_exclude)
|
57
|
+
@default_inflection = args[:inflection]
|
58
|
+
@inflection_header = args.fetch(:inflection_header, 'Key-Inflection').gsub(/[^a-z0-9]/i, '_').upcase
|
59
|
+
@inflection_header = "HTTP_#{@inflection_header}" unless @inflection_header.start_with?('HTTP_')
|
60
|
+
end
|
61
|
+
|
62
|
+
def call(env)
|
63
|
+
Transformations.underscore_params(env) unless exclude_params?(env)
|
64
|
+
status, headers, response = @app.call(env)
|
65
|
+
|
66
|
+
return [status, headers, response] if exclude_response?(env, headers)
|
67
|
+
|
68
|
+
new_responses = []
|
69
|
+
|
70
|
+
response.each do |body|
|
71
|
+
begin
|
72
|
+
new_response = MultiJson.load(body)
|
73
|
+
rescue MultiJson::ParseError
|
74
|
+
new_responses << body
|
75
|
+
next
|
76
|
+
end
|
77
|
+
|
78
|
+
Transformations.transform(new_response, inflection_method(env))
|
79
|
+
|
80
|
+
new_responses << MultiJson.dump(new_response)
|
81
|
+
end
|
82
|
+
|
83
|
+
[status, headers, new_responses]
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def exclude_params?(env)
|
89
|
+
exclude?(env, env["CONTENT_TYPE"], @exclude_params)
|
90
|
+
end
|
91
|
+
|
92
|
+
def exclude_response?(env, headers)
|
93
|
+
exclude_rails_route?(env) ||
|
94
|
+
exclude?(env, headers['Content-Type'], @exclude_response)
|
95
|
+
end
|
96
|
+
|
97
|
+
def exclude?(env, content_type, block)
|
98
|
+
!inflection_type(env) || !valid_content_type?(content_type) || block.call(env)
|
99
|
+
end
|
100
|
+
|
101
|
+
def exclude_rails_route?(env)
|
102
|
+
env['PATH_INFO'].to_s.start_with?('/rails')
|
103
|
+
end
|
104
|
+
|
105
|
+
def valid_content_type?(content_type)
|
106
|
+
@content_type_check.call(content_type)
|
107
|
+
end
|
108
|
+
|
109
|
+
def inflection_type(env)
|
110
|
+
env[@inflection_header] || @default_inflection
|
111
|
+
end
|
112
|
+
|
113
|
+
def inflection_method(env)
|
114
|
+
inflection = inflection_type(env)
|
115
|
+
|
116
|
+
if inflection == "camel"
|
117
|
+
@camelize
|
118
|
+
elsif inflection == "dash"
|
119
|
+
@dasherize
|
120
|
+
elsif inflection == 'pascal'
|
121
|
+
@pascalize
|
122
|
+
else
|
123
|
+
# probably misconfigured, do nothing
|
124
|
+
-> (string) { string }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/lib/olive_branch.rb
ADDED
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: privatenote_olive_branch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Privatenote
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-02-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: multi_json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.5.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.5.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: appraisal
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-rails
|
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
|
+
description: Handle camel/snake/dash case conversion. forked from vigetlabs/olive_branch.
|
70
|
+
email:
|
71
|
+
- dev@privatenote.co.kr
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- MIT-LICENSE
|
77
|
+
- README.md
|
78
|
+
- lib/olive_branch.rb
|
79
|
+
- lib/olive_branch/middleware.rb
|
80
|
+
- lib/olive_branch/version.rb
|
81
|
+
homepage: https://github.com/privatenote/olive_branch
|
82
|
+
licenses:
|
83
|
+
- MIT
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubygems_version: 3.3.7
|
101
|
+
signing_key:
|
102
|
+
specification_version: 4
|
103
|
+
summary: Handle camel/snake/dash case conversion
|
104
|
+
test_files: []
|