camille 0.4.0 → 0.4.2
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 +4 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +1 -2
- data/README.md +13 -4
- data/gemfiles/rails-6.1 +9 -0
- data/gemfiles/rails-7.0 +9 -0
- data/lib/camille/basic_type.rb +9 -0
- data/lib/camille/controller_extension.rb +5 -4
- data/lib/camille/custom_types/date.rb +31 -0
- data/lib/camille/generators/install_generator.rb +3 -2
- data/lib/camille/generators/templates/date_time.rb +12 -0
- data/lib/camille/generators/templates/decimal.rb +16 -0
- data/lib/camille/loader.rb +17 -5
- data/lib/camille/main_controller.rb +1 -0
- data/lib/camille/pick_and_omit.rb +51 -0
- data/lib/camille/type.rb +5 -0
- data/lib/camille/types/array.rb +5 -0
- data/lib/camille/types/number_literal.rb +2 -0
- data/lib/camille/types/object.rb +23 -0
- data/lib/camille/types/omit.rb +24 -0
- data/lib/camille/types/pick.rb +24 -0
- data/lib/camille/types/string_literal.rb +2 -0
- data/lib/camille/types/union.rb +20 -0
- data/lib/camille/version.rb +1 -1
- data/lib/camille.rb +3 -0
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1c6d8ec2d346e2ff02811be47d900bee304a82f5a96e213cbf5e1c9f96f3fe61
|
4
|
+
data.tar.gz: 1a67d813067334b228caa09c9cc10411fa4a3b56eab57610b415c129616a4533
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d977978049f5c5589731135c24c360145c3145d3ded0be2d158ea67546eece82d8af7c07674e1895d03a3071418adc9b1a6a597e5246b22b96d0c7abb2123d3
|
7
|
+
data.tar.gz: b4fd67e56fdd11db3c00d1e1ddee3a775c8e3d87ba2423cbe652729ed3dfb3460591d658a89bd1192385c45f0507fbeae5172a03acac65fcb3ad0880fe3b1aa7
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
## 0.4.2
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
* Supported `transform` for custom types
|
8
|
+
* Added default DateTime and Decimal type to install generator
|
9
|
+
|
10
|
+
## 0.4.1
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
* Added utility types Pick and Omit
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
|
18
|
+
* Prevent loader errors from being silently rescued by Rails in development environment
|
19
|
+
|
20
|
+
## 0.4.0
|
21
|
+
|
22
|
+
The first stable release
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
## Why?
|
4
4
|
|
5
|
-
Traditionally, the JSON response from a Rails API server isn't typed. So even if we have TypeScript at the front-end, we still have little guarantee that our back-end would return the correct type and structure of data.
|
5
|
+
Traditionally, the JSON response from a Rails API server isn't typed. So even if we have TypeScript at the front-end, we still have little guarantee that our back-end would return the correct type and structure of data.
|
6
|
+
|
7
|
+
In order to eliminate type mismatch between both ends, Camille provides a syntax for you to define type schema for your Rails API, and uses these schemas to generate the TypeScript functions for calling the API.
|
6
8
|
|
7
9
|
For example, an endpoint defined in Ruby, where `data` is a controller action,
|
8
10
|
|
@@ -84,6 +86,8 @@ data(params: {id: number}): Promise<{name: string}>
|
|
84
86
|
|
85
87
|
Therefore, the front-end user is required to provide an `id` when they call this function. And they can expect to get a `name` from the response of this request. There are no more type mismatch between both ends.
|
86
88
|
|
89
|
+
The `params` type for an endpoint is required to be an object type, or a hash in Ruby, while `response` type can be any supported type, for example a `Boolean`.
|
90
|
+
|
87
91
|
Camille will automatically add a Rails route for each endpoint. You don't need to do anything other than having the schema file in place.
|
88
92
|
|
89
93
|
When defining an endpoint, you can also use `post` instead of `get` for non-idempotent requests. However, no other HTTP verbs are supported, because verbs in RESTful like `patch` and `delete` indicate what we do on resources, but in RPC-style design each request is merely a function call that does not concern RESTful resources.
|
@@ -153,12 +157,13 @@ params(
|
|
153
157
|
number_literal: 1,
|
154
158
|
string_literal: 'hello',
|
155
159
|
# a custom type we defined above
|
156
|
-
product: Product
|
160
|
+
product: Product,
|
161
|
+
# Pick and Omit use [] to enclose parameters instead of <>
|
162
|
+
pick: Pick[{a: 1, b: 2}, 'a' | 'b'],
|
163
|
+
omit: Omit[Product, 'id']
|
157
164
|
)
|
158
165
|
```
|
159
166
|
|
160
|
-
String literal types and probably enums are planned for the future.
|
161
|
-
|
162
167
|
### TypeScript generation
|
163
168
|
|
164
169
|
After you have your types and schemas in place, you can visit `/camille/endpoints.ts` in development environment to have the TypeScript request functions generated.
|
@@ -235,6 +240,10 @@ object:
|
|
235
240
|
[2]: Expected number, got "3".
|
236
241
|
```
|
237
242
|
|
243
|
+
### Reloading
|
244
|
+
|
245
|
+
Everything in `config/camille/types` and `config/camille/schemas` will automatically reload after changes in development environment, just like other files in Rails.
|
246
|
+
|
238
247
|
## Development
|
239
248
|
|
240
249
|
Run tests with `bundle exec rake`.
|
data/gemfiles/rails-6.1
ADDED
data/gemfiles/rails-7.0
ADDED
data/lib/camille/basic_type.rb
CHANGED
@@ -11,6 +11,15 @@ module Camille
|
|
11
11
|
Camille::Types::Array.new(self)
|
12
12
|
end
|
13
13
|
|
14
|
+
def transform_and_check value
|
15
|
+
transformed = transform value
|
16
|
+
[check(value), transformed]
|
17
|
+
end
|
18
|
+
|
19
|
+
def transform value
|
20
|
+
value
|
21
|
+
end
|
22
|
+
|
14
23
|
def self.| other
|
15
24
|
Camille::Type.instance(self) | other
|
16
25
|
end
|
@@ -15,16 +15,16 @@ module Camille
|
|
15
15
|
if endpoint = camille_endpoint
|
16
16
|
render_options = args.last
|
17
17
|
if value = render_options[:json]
|
18
|
-
error = endpoint.response_type.
|
18
|
+
error, transformed = endpoint.response_type.transform_and_check(value)
|
19
19
|
if error
|
20
20
|
string_io = StringIO.new
|
21
21
|
Camille::TypeErrorPrinter.new(error).print(string_io)
|
22
22
|
raise TypeError.new("\nType check failed for response.\n#{string_io.string}")
|
23
23
|
else
|
24
|
-
if
|
25
|
-
|
24
|
+
if transformed.is_a? Hash
|
25
|
+
transformed.deep_transform_keys!{|k| k.to_s.camelize(:lower)}
|
26
26
|
end
|
27
|
-
super(json:
|
27
|
+
super(json: transformed)
|
28
28
|
end
|
29
29
|
else
|
30
30
|
raise ArgumentError.new("Expected key :json for `render` call.")
|
@@ -35,6 +35,7 @@ module Camille
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def process_action(*)
|
38
|
+
Camille::Loader.check_and_raise_exception
|
38
39
|
if endpoint = camille_endpoint
|
39
40
|
params.deep_transform_keys!{|key| key.to_s.underscore}
|
40
41
|
super
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
# This type is only for testing, to simulate the usage of transform.
|
4
|
+
module Camille
|
5
|
+
module CustomTypes
|
6
|
+
class Date < Camille::Type
|
7
|
+
include Camille::Types
|
8
|
+
|
9
|
+
alias_of(
|
10
|
+
type: 'Date',
|
11
|
+
value: Number
|
12
|
+
)
|
13
|
+
|
14
|
+
def transform value
|
15
|
+
case value
|
16
|
+
when ::Date
|
17
|
+
timestamp = value.to_time.to_i
|
18
|
+
when ::Time
|
19
|
+
timestamp = value.to_i
|
20
|
+
end
|
21
|
+
{
|
22
|
+
type: 'Date',
|
23
|
+
value: timestamp.to_i * 1000
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
Camille::Loader.loaded_types.delete(Camille::CustomTypes::Date)
|
@@ -10,8 +10,9 @@ module Camille
|
|
10
10
|
copy_file "configuration.rb", "config/camille/configuration.rb"
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
14
|
-
copy_file ".
|
13
|
+
def create_date_time_and_decimal
|
14
|
+
copy_file "date_time.rb", "config/camille/types/date_time.rb"
|
15
|
+
copy_file "decimal.rb", "config/camille/types/decimal.rb"
|
15
16
|
end
|
16
17
|
|
17
18
|
def create_schemas_folder
|
@@ -0,0 +1,16 @@
|
|
1
|
+
using Camille::Syntax
|
2
|
+
|
3
|
+
class Camille::Types::Decimal < Camille::Type
|
4
|
+
include Camille::Types
|
5
|
+
|
6
|
+
alias_of(Number)
|
7
|
+
|
8
|
+
# transforms a BigDecimal into a Float so it fits in Number type
|
9
|
+
def transform value
|
10
|
+
if value.is_a? BigDecimal
|
11
|
+
value.to_f
|
12
|
+
else
|
13
|
+
value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/camille/loader.rb
CHANGED
@@ -41,11 +41,23 @@ module Camille
|
|
41
41
|
|
42
42
|
def reload_types_and_schemas
|
43
43
|
synchronize do
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
begin
|
45
|
+
@exception = nil
|
46
|
+
Camille::Loader.loaded_types.clear
|
47
|
+
Camille::Loader.loaded_schemas.clear
|
48
|
+
@zeitwerk_loader.reload
|
49
|
+
eager_load
|
50
|
+
construct_controller_name_to_schema_map
|
51
|
+
rescue Exception => e
|
52
|
+
@exception = e
|
53
|
+
raise e
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def check_and_raise_exception
|
59
|
+
if @exception
|
60
|
+
raise @exception
|
49
61
|
end
|
50
62
|
end
|
51
63
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Camille
|
2
|
+
# shared base class for Pick and Omit
|
3
|
+
class PickAndOmit < Camille::BasicType
|
4
|
+
class ArgumentError < ::ArgumentError; end
|
5
|
+
|
6
|
+
def initialize type, keys
|
7
|
+
@type = Camille::Type.instance(type)
|
8
|
+
case
|
9
|
+
when @type.is_a?(Camille::Types::Object)
|
10
|
+
@target_object = @type
|
11
|
+
when @type.is_a?(Camille::Type) && @type.underlying.is_a?(Camille::Types::Object)
|
12
|
+
@target_object = @type.underlying
|
13
|
+
else
|
14
|
+
raise ArgumentError.new("Currently onle an object type or an alias of object type is supported in #{klass_name}. Got #{type.inspect}.")
|
15
|
+
end
|
16
|
+
|
17
|
+
@keys = Camille::Type.instance(keys)
|
18
|
+
case
|
19
|
+
when @keys.is_a?(Camille::Types::StringLiteral)
|
20
|
+
@keys_array = [@keys.value].map(&:to_sym)
|
21
|
+
when @keys.is_a?(Camille::Types::Union)
|
22
|
+
unfolded = unfold_union(@keys).flatten
|
23
|
+
if unfolded.all?{|x| x.is_a?(Camille::Types::StringLiteral)}
|
24
|
+
@keys_array = unfolded.map(&:value).map(&:to_sym)
|
25
|
+
else
|
26
|
+
raise ArgumentError.new("The second argument of #{klass_name} has to be a string literal or an union of string literals. Got #{keys.inspect}.")
|
27
|
+
end
|
28
|
+
else
|
29
|
+
raise ArgumentError.new("The second argument of #{klass_name} has to be a string literal or an union of string literals. Got #{keys.inspect}.")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def literal
|
34
|
+
"#{klass_name}<#{@type.literal}, #{@keys.literal}>"
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.[] type, keys
|
38
|
+
self.new type, keys
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def unfold_union type
|
43
|
+
if type.is_a?(Camille::Types::Union)
|
44
|
+
[unfold_union(type.left), unfold_union(type.right)]
|
45
|
+
else
|
46
|
+
[type]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
data/lib/camille/type.rb
CHANGED
data/lib/camille/types/array.rb
CHANGED
data/lib/camille/types/object.rb
CHANGED
@@ -21,6 +21,29 @@ module Camille
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
def transform_and_check value
|
25
|
+
if value.is_a? Hash
|
26
|
+
transform_and_check_results = @fields.map do |key, type|
|
27
|
+
[key, type.transform_and_check(value[key])]
|
28
|
+
end
|
29
|
+
|
30
|
+
errors = transform_and_check_results.map do |key, (error, transformed)|
|
31
|
+
[key.to_s, error]
|
32
|
+
end.select{|x| x[1]}
|
33
|
+
|
34
|
+
if errors.empty?
|
35
|
+
transformed = transform_and_check_results.map do |key, (error, transformed)|
|
36
|
+
[key, transformed]
|
37
|
+
end.to_h
|
38
|
+
[nil, transformed]
|
39
|
+
else
|
40
|
+
Camille::TypeError.new(**errors.to_h)
|
41
|
+
end
|
42
|
+
else
|
43
|
+
Camille::TypeError.new("Expected hash, got #{value.inspect}.")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
24
47
|
def literal
|
25
48
|
"{#{@fields.map{|k,v| "#{ActiveSupport::Inflector.camelize k.to_s, false}: #{v.literal}"}.join(', ')}}"
|
26
49
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Camille
|
2
|
+
module Types
|
3
|
+
class Omit < PickAndOmit
|
4
|
+
def check value
|
5
|
+
processed_object.check(value)
|
6
|
+
end
|
7
|
+
|
8
|
+
def transform_and_check value
|
9
|
+
processed_object.transform_and_check(value)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def klass_name
|
14
|
+
'Omit'
|
15
|
+
end
|
16
|
+
|
17
|
+
def processed_object
|
18
|
+
fields = @target_object.fields.reject{|k, _| @keys_array.include?(k)}
|
19
|
+
Camille::Types::Object.new(fields)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Camille
|
2
|
+
module Types
|
3
|
+
class Pick < PickAndOmit
|
4
|
+
def check value
|
5
|
+
processed_object.check(value)
|
6
|
+
end
|
7
|
+
|
8
|
+
def transform_and_check value
|
9
|
+
processed_object.transform_and_check(value)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def klass_name
|
14
|
+
'Pick'
|
15
|
+
end
|
16
|
+
|
17
|
+
def processed_object
|
18
|
+
fields = @target_object.fields.select{|k, _| @keys_array.include?(k)}
|
19
|
+
Camille::Types::Object.new(fields)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/camille/types/union.rb
CHANGED
@@ -21,6 +21,26 @@ module Camille
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
def transform_and_check value
|
25
|
+
left_transformed = @left.transform value
|
26
|
+
left_result = @left.check left_transformed
|
27
|
+
if left_result
|
28
|
+
right_transformed = @right.transform value
|
29
|
+
right_result = @right.check right_transformed
|
30
|
+
if right_result
|
31
|
+
error = Camille::TypeError.new(
|
32
|
+
'union.left' => left_result,
|
33
|
+
'union.right' => right_result
|
34
|
+
)
|
35
|
+
[error, nil]
|
36
|
+
else
|
37
|
+
[nil, right_transformed]
|
38
|
+
end
|
39
|
+
else
|
40
|
+
[nil, left_transformed]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
24
44
|
def literal
|
25
45
|
"#{@left.literal} | #{@right.literal}"
|
26
46
|
end
|
data/lib/camille/version.rb
CHANGED
data/lib/camille.rb
CHANGED
@@ -17,6 +17,9 @@ require_relative "camille/types/tuple"
|
|
17
17
|
require_relative "camille/types/any"
|
18
18
|
require_relative "camille/types/number_literal"
|
19
19
|
require_relative "camille/types/string_literal"
|
20
|
+
require_relative "camille/pick_and_omit"
|
21
|
+
require_relative "camille/types/pick"
|
22
|
+
require_relative "camille/types/omit"
|
20
23
|
require_relative "camille/type"
|
21
24
|
require_relative "camille/type_error"
|
22
25
|
require_relative "camille/type_error_printer"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: camille
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- 辻彩
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-03-
|
11
|
+
date: 2023-03-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -38,28 +38,35 @@ extensions: []
|
|
38
38
|
extra_rdoc_files: []
|
39
39
|
files:
|
40
40
|
- ".rspec"
|
41
|
+
- CHANGELOG.md
|
41
42
|
- Gemfile
|
42
43
|
- Gemfile.lock
|
43
44
|
- README.md
|
44
45
|
- Rakefile
|
45
46
|
- bin/console
|
46
47
|
- bin/setup
|
48
|
+
- gemfiles/rails-6.1
|
49
|
+
- gemfiles/rails-7.0
|
47
50
|
- lib/camille.rb
|
48
51
|
- lib/camille/basic_type.rb
|
49
52
|
- lib/camille/code_generator.rb
|
50
53
|
- lib/camille/configuration.rb
|
51
54
|
- lib/camille/controller_extension.rb
|
55
|
+
- lib/camille/custom_types/date.rb
|
52
56
|
- lib/camille/endpoint.rb
|
53
57
|
- lib/camille/generators/install_generator.rb
|
54
58
|
- lib/camille/generators/schema_generator.rb
|
55
59
|
- lib/camille/generators/templates/.keep
|
56
60
|
- lib/camille/generators/templates/configuration.rb
|
61
|
+
- lib/camille/generators/templates/date_time.rb
|
62
|
+
- lib/camille/generators/templates/decimal.rb
|
57
63
|
- lib/camille/generators/templates/schema_template.erb
|
58
64
|
- lib/camille/generators/templates/type_template.erb
|
59
65
|
- lib/camille/generators/type_generator.rb
|
60
66
|
- lib/camille/line.rb
|
61
67
|
- lib/camille/loader.rb
|
62
68
|
- lib/camille/main_controller.rb
|
69
|
+
- lib/camille/pick_and_omit.rb
|
63
70
|
- lib/camille/railtie.rb
|
64
71
|
- lib/camille/schema.rb
|
65
72
|
- lib/camille/schemas.rb
|
@@ -75,6 +82,8 @@ files:
|
|
75
82
|
- lib/camille/types/number.rb
|
76
83
|
- lib/camille/types/number_literal.rb
|
77
84
|
- lib/camille/types/object.rb
|
85
|
+
- lib/camille/types/omit.rb
|
86
|
+
- lib/camille/types/pick.rb
|
78
87
|
- lib/camille/types/string.rb
|
79
88
|
- lib/camille/types/string_literal.rb
|
80
89
|
- lib/camille/types/tuple.rb
|