camille 0.3.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +1 -2
- data/README.md +229 -11
- data/gemfiles/rails-6.1 +9 -0
- data/gemfiles/rails-7.0 +9 -0
- data/lib/camille/basic_type.rb +9 -4
- data/lib/camille/controller_extension.rb +4 -2
- data/lib/camille/generators/install_generator.rb +4 -4
- data/lib/camille/generators/templates/.keep +0 -0
- data/lib/camille/generators/templates/schema_template.erb +1 -1
- data/lib/camille/generators/templates/type_template.erb +4 -4
- 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/syntax.rb +75 -0
- data/lib/camille/type_error_printer.rb +2 -2
- data/lib/camille/types/number.rb +1 -1
- data/lib/camille/types/number_literal.rb +27 -0
- data/lib/camille/types/omit.rb +20 -0
- data/lib/camille/types/pick.rb +20 -0
- data/lib/camille/types/string_literal.rb +27 -0
- data/lib/camille/version.rb +1 -1
- data/lib/camille.rb +6 -1
- metadata +12 -5
- data/lib/camille/core_ext.rb +0 -33
- data/lib/camille/generators/templates/schema_example.rb +0 -35
- data/lib/camille/generators/templates/type_example.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2370ceb41fe8fdd701b99671f1d4d82678fc103218e96a10a52e60b9b2d1ad1f
|
4
|
+
data.tar.gz: ef8fe63ad679a1744fdef8c58971148d3642be0be2c5e23ff53b510dfef2b449
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6b8068692bd238a82cded511bad5e15d3f0993640a487b335cfd7d1592fe3c8cb45f88924753a1b580ae7448a59d58e423c29332842fa9be09472585bc810ea7
|
7
|
+
data.tar.gz: 71bc242a3a3fc9224c0d2e7d3598d9e4be378ff7b2a028e02b55833927cfb516d6309c44bbc7b2296b0b9b780ffe07458435e40a89a1953d6044a465621e7ef9
|
data/CHANGELOG.md
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,33 @@
|
|
1
1
|
# Camille
|
2
2
|
|
3
|
-
|
3
|
+
## Why?
|
4
4
|
|
5
|
-
|
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.
|
8
|
+
|
9
|
+
For example, an endpoint defined in Ruby, where `data` is a controller action,
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
get :data do
|
13
|
+
params(
|
14
|
+
id: Number
|
15
|
+
)
|
16
|
+
response(
|
17
|
+
name: String
|
18
|
+
)
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
will become a function in TypeScript:
|
23
|
+
|
24
|
+
```typescript
|
25
|
+
data(params: {id: number}): Promise<{name: string}>
|
26
|
+
```
|
27
|
+
|
28
|
+
Therefore, if the front-end requests the API by calling `data`, we have guarantee that `id` is presented in `params`, and Camille will require the response to contain a string `name`, so the front-end can receive the correct type of data.
|
29
|
+
|
30
|
+
By using these request functions, we also don't need to know about HTTP verbs and paths. It's impossible to have unrecognized routes, since Camille will make sure that each function handled by the correct Rails action.
|
6
31
|
|
7
32
|
## Installation
|
8
33
|
|
@@ -14,22 +39,215 @@ gem 'camille'
|
|
14
39
|
|
15
40
|
And then execute:
|
16
41
|
|
17
|
-
|
42
|
+
```bash
|
43
|
+
bundle install
|
44
|
+
bundle exec rails g camille:install
|
45
|
+
```
|
18
46
|
|
19
|
-
|
47
|
+
## Usage
|
20
48
|
|
21
|
-
|
49
|
+
### Schemas
|
22
50
|
|
23
|
-
|
51
|
+
A schema defines the type of `params` and `response` for a controller action. The following commands will generate schema definition files in `config/camille/schemas`.
|
24
52
|
|
25
|
-
|
53
|
+
```bash
|
54
|
+
# to generate a schema for ProductsController
|
55
|
+
bundle exec rails g camille:schema products
|
56
|
+
# to generate a schema for Api::ProductController
|
57
|
+
bundle exec rails g camille:schema api/products
|
58
|
+
```
|
26
59
|
|
27
|
-
|
60
|
+
An example of schema definition:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
using Camille::Syntax
|
64
|
+
|
65
|
+
class Camille::Schemas::Api::Products < Camille::Schema
|
66
|
+
include Camille::Types
|
67
|
+
|
68
|
+
get :data do
|
69
|
+
params(
|
70
|
+
id: Number
|
71
|
+
)
|
72
|
+
response(
|
73
|
+
name: String
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
The `Api::Products` schema defines one endpoint `data` and its params and response type. This endpoint corresponds to the `data` action on `Api::ProductsController`. Inside the action, you can assume that `params[:id]` is a number, and you will need to `render json: {name: 'some string'}` in order to pass the typecheck.
|
80
|
+
|
81
|
+
When generating TypeScript request functions, the `data` endpoint will become a function having the following signature:
|
82
|
+
|
83
|
+
```typescript
|
84
|
+
data(params: {id: number}): Promise<{name: string}>
|
85
|
+
```
|
86
|
+
|
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.
|
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
|
+
|
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.
|
92
|
+
|
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.
|
94
|
+
|
95
|
+
### Custom types
|
96
|
+
|
97
|
+
In addition to primitive types, you can define custom types in Camille. The following commands will generate type definition files in `config/camille/types`.
|
98
|
+
|
99
|
+
```bash
|
100
|
+
# to generate a type named Product
|
101
|
+
rails g camille:type product
|
102
|
+
# to generate a type named Nested::Product
|
103
|
+
rails g camille:type nested/product
|
104
|
+
```
|
105
|
+
|
106
|
+
An example of custom type definition:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
using Camille::Syntax
|
110
|
+
|
111
|
+
class Camille::Types::Product < Camille::Type
|
112
|
+
include Camille::Types
|
113
|
+
|
114
|
+
alias_of(
|
115
|
+
id: Number,
|
116
|
+
name: String
|
117
|
+
)
|
118
|
+
end
|
119
|
+
```
|
120
|
+
|
121
|
+
Each custom type is considered a type alias in TypeScript. And `alias_of` defines what this type is aliasing. In this case, the `Product` type is an alias of an object type having fields `id` as `Number` and `name` as `String`. When generating TypeScript, it will be converted to the following:
|
122
|
+
|
123
|
+
```typescript
|
124
|
+
type Product = {id: number, name: string}
|
125
|
+
```
|
126
|
+
|
127
|
+
### Available syntax for types
|
128
|
+
|
129
|
+
Camille supports most of the type syntax in TypeScript. Below is a list of types that you can use in type and schema definition.
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
params(
|
133
|
+
# primitive types in TypeScript
|
134
|
+
number: Number,
|
135
|
+
string: String,
|
136
|
+
boolean: Boolean,
|
137
|
+
null: Null,
|
138
|
+
undefined: Undefined,
|
139
|
+
any: Any,
|
140
|
+
# an array type is a type name followed by '[]'
|
141
|
+
array: Number[],
|
142
|
+
# an object type looks like hash
|
143
|
+
object: {
|
144
|
+
field: Number
|
145
|
+
},
|
146
|
+
# an array of objects also works
|
147
|
+
object_array: {
|
148
|
+
field: Number
|
149
|
+
}[]
|
150
|
+
# a union type is two types connected by '|'
|
151
|
+
union: Number | String,
|
152
|
+
# a tuple type is several types put inside '[]'
|
153
|
+
tuple: [Number, String, Boolean],
|
154
|
+
# a field followed by '?' is optional, the same as in TypeScript
|
155
|
+
optional?: Number,
|
156
|
+
# literal types
|
157
|
+
number_literal: 1,
|
158
|
+
string_literal: 'hello',
|
159
|
+
# a custom type we defined above
|
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']
|
164
|
+
)
|
165
|
+
```
|
166
|
+
|
167
|
+
### TypeScript generation
|
168
|
+
|
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.
|
170
|
+
|
171
|
+
An example from our previously defined type and schema will be:
|
172
|
+
|
173
|
+
```typescript
|
174
|
+
import request from './request'
|
175
|
+
|
176
|
+
export type Product = {id: number, name: string}
|
177
|
+
|
178
|
+
export default {
|
179
|
+
api: {
|
180
|
+
data(params: {id: number}): Promise<{name: string}> {
|
181
|
+
return request('get', '/api/products/data', params)
|
182
|
+
}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
```
|
186
|
+
|
187
|
+
The first line of `import` is configurable as `config.ts_header` in `config/camille/configuration.rb`. You would need to implement a `request` function that performs the HTTP request.
|
188
|
+
|
189
|
+
### Conversion between camelCase and snake_case
|
190
|
+
|
191
|
+
In TypeScript world, people usually use camelCase to name functions and variables, while in Ruby the convention is to use snake_case. Camille will automatically convert between these two when processing request.
|
192
|
+
|
193
|
+
For example,
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
get :special_data do
|
197
|
+
params(
|
198
|
+
long_id: Number
|
199
|
+
)
|
200
|
+
response(
|
201
|
+
long_name: String
|
202
|
+
)
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
206
|
+
will have TS signature:
|
207
|
+
|
208
|
+
```typescript
|
209
|
+
specialData(params: {longId: number}): Promise<{longName: string}>
|
210
|
+
```
|
211
|
+
|
212
|
+
In the Rails action you still use `params[:long_id]` to access the parameter and return `long_name` in response.
|
213
|
+
|
214
|
+
### Typechecking
|
215
|
+
|
216
|
+
If a controller action has a corresponding schema, Camille will raise an error if the returned JSON doesn't match the response type specified in the schema.
|
217
|
+
|
218
|
+
For example for
|
219
|
+
```ruby
|
220
|
+
response(
|
221
|
+
object: {
|
222
|
+
array: Number[]
|
223
|
+
}
|
224
|
+
)
|
225
|
+
```
|
28
226
|
|
29
|
-
|
227
|
+
if we return such a JSON in our action
|
228
|
+
```ruby
|
229
|
+
render json: {
|
230
|
+
object: {
|
231
|
+
array: [1, 2, '3']
|
232
|
+
}
|
233
|
+
}
|
234
|
+
```
|
235
|
+
|
236
|
+
Camille will print the following error:
|
237
|
+
```
|
238
|
+
object:
|
239
|
+
array:
|
240
|
+
[2]: Expected number, got "3".
|
241
|
+
```
|
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
|
+
|
247
|
+
## Development
|
30
248
|
|
31
|
-
|
249
|
+
Run tests with `bundle exec rake`.
|
32
250
|
|
33
251
|
## Contributing
|
34
252
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
253
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/onyxblade/camille.
|
data/gemfiles/rails-6.1
ADDED
data/gemfiles/rails-7.0
ADDED
data/lib/camille/basic_type.rb
CHANGED
@@ -24,13 +24,18 @@ module Camille
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def self.instance value
|
27
|
-
|
27
|
+
case
|
28
|
+
when value.is_a?(::Hash)
|
28
29
|
Camille::Types::Object.new(value)
|
29
|
-
|
30
|
+
when value.is_a?(::Array)
|
30
31
|
Camille::Types::Tuple.new(value)
|
31
|
-
|
32
|
+
when value.is_a?(Integer) || value.is_a?(Float)
|
33
|
+
Camille::Types::NumberLiteral.new(value)
|
34
|
+
when value.is_a?(::String)
|
35
|
+
Camille::Types::StringLiteral.new(value)
|
36
|
+
when value.is_a?(Camille::BasicType)
|
32
37
|
value
|
33
|
-
|
38
|
+
when value.is_a?(Class) && value < Camille::BasicType && value.directly_instantiable?
|
34
39
|
value.new
|
35
40
|
else
|
36
41
|
raise InvalidTypeError.new("#{value} cannot be converted to a type instance.")
|
@@ -17,8 +17,9 @@ module Camille
|
|
17
17
|
if value = render_options[:json]
|
18
18
|
error = endpoint.response_type.check(value)
|
19
19
|
if error
|
20
|
-
|
21
|
-
|
20
|
+
string_io = StringIO.new
|
21
|
+
Camille::TypeErrorPrinter.new(error).print(string_io)
|
22
|
+
raise TypeError.new("\nType check failed for response.\n#{string_io.string}")
|
22
23
|
else
|
23
24
|
if value.is_a? Hash
|
24
25
|
value.deep_transform_keys!{|k| k.to_s.camelize(:lower)}
|
@@ -34,6 +35,7 @@ module Camille
|
|
34
35
|
end
|
35
36
|
|
36
37
|
def process_action(*)
|
38
|
+
Camille::Loader.check_and_raise_exception
|
37
39
|
if endpoint = camille_endpoint
|
38
40
|
params.deep_transform_keys!{|key| key.to_s.underscore}
|
39
41
|
super
|
@@ -10,12 +10,12 @@ 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_types_folder
|
14
|
+
copy_file ".keep", "config/camille/types/.keep"
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
copy_file "
|
17
|
+
def create_schemas_folder
|
18
|
+
copy_file ".keep", "config/camille/schemas/.keep"
|
19
19
|
end
|
20
20
|
|
21
21
|
end
|
File without changes
|
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
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Camille
|
2
|
+
module Syntax
|
3
|
+
module NULL_VALUE; end
|
4
|
+
|
5
|
+
refine ::Hash do
|
6
|
+
def [] key = NULL_VALUE
|
7
|
+
if key == NULL_VALUE
|
8
|
+
Camille::Types::Object.new(self)[]
|
9
|
+
else
|
10
|
+
super
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def | other
|
15
|
+
Camille::Types::Union.new(Camille::Types::Object.new(self), other)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
refine ::Array do
|
20
|
+
def [] key = NULL_VALUE
|
21
|
+
if key == NULL_VALUE
|
22
|
+
Camille::Types::Tuple.new(self)[]
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def | other
|
29
|
+
Camille::Types::Union.new(Camille::Types::Tuple.new(self), other)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
refine Integer do
|
34
|
+
def [] key = NULL_VALUE
|
35
|
+
if key == NULL_VALUE
|
36
|
+
Camille::Types::NumberLiteral.new(self)[]
|
37
|
+
else
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def | other
|
43
|
+
Camille::Types::Union.new(Camille::Types::NumberLiteral.new(self), other)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
refine Float do
|
48
|
+
def [] key = NULL_VALUE
|
49
|
+
if key == NULL_VALUE
|
50
|
+
Camille::Types::NumberLiteral.new(self)[]
|
51
|
+
else
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def | other
|
57
|
+
Camille::Types::Union.new(Camille::Types::NumberLiteral.new(self), other)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
refine ::String do
|
62
|
+
def [] key = NULL_VALUE
|
63
|
+
if key == NULL_VALUE
|
64
|
+
Camille::Types::StringLiteral.new(self)[]
|
65
|
+
else
|
66
|
+
super
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def | other
|
71
|
+
Camille::Types::Union.new(Camille::Types::StringLiteral.new(self), other)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -18,9 +18,9 @@ module Camille
|
|
18
18
|
def print_composite_error io, error, indentation
|
19
19
|
error.components.each do |key, error|
|
20
20
|
if error.basic?
|
21
|
-
io.puts
|
21
|
+
io.puts "\u00A0" * indentation + "#{key}: #{error.message}"
|
22
22
|
else
|
23
|
-
io.puts
|
23
|
+
io.puts "\u00A0" * indentation + "#{key}:"
|
24
24
|
print_composite_error io, error, indentation + 2
|
25
25
|
end
|
26
26
|
end
|
data/lib/camille/types/number.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Camille
|
2
|
+
module Types
|
3
|
+
class NumberLiteral < Camille::BasicType
|
4
|
+
class ArgumentError < ::ArgumentError; end
|
5
|
+
|
6
|
+
attr_reader :value
|
7
|
+
|
8
|
+
def initialize value
|
9
|
+
if value.is_a?(Integer) || value.is_a?(Float)
|
10
|
+
@value = value
|
11
|
+
else
|
12
|
+
raise ArgumentError.new("Expecting an integer or a float, got #{value.inspect}")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def check value
|
17
|
+
unless value == @value
|
18
|
+
Camille::TypeError.new("Expected number literal #{@value.inspect}, got #{value.inspect}.")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def literal
|
23
|
+
@value.to_s
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Camille
|
2
|
+
module Types
|
3
|
+
class Omit < PickAndOmit
|
4
|
+
def check value
|
5
|
+
processed_object.check(value)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
def klass_name
|
10
|
+
'Omit'
|
11
|
+
end
|
12
|
+
|
13
|
+
def processed_object
|
14
|
+
fields = @target_object.fields.reject{|k, _| @keys_array.include?(k)}
|
15
|
+
Camille::Types::Object.new(fields)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Camille
|
2
|
+
module Types
|
3
|
+
class Pick < PickAndOmit
|
4
|
+
def check value
|
5
|
+
processed_object.check(value)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
def klass_name
|
10
|
+
'Pick'
|
11
|
+
end
|
12
|
+
|
13
|
+
def processed_object
|
14
|
+
fields = @target_object.fields.select{|k, _| @keys_array.include?(k)}
|
15
|
+
Camille::Types::Object.new(fields)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Camille
|
2
|
+
module Types
|
3
|
+
class StringLiteral < Camille::BasicType
|
4
|
+
class ArgumentError < ::ArgumentError; end
|
5
|
+
|
6
|
+
attr_reader :value
|
7
|
+
|
8
|
+
def initialize value
|
9
|
+
if value.is_a?(::String)
|
10
|
+
@value = value
|
11
|
+
else
|
12
|
+
raise ArgumentError.new("Expecting a string, got #{value.inspect}")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def check value
|
17
|
+
unless value == @value
|
18
|
+
Camille::TypeError.new("Expected string literal #{@value.inspect}, got #{value.inspect}.")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def literal
|
23
|
+
@value.inspect
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/camille/version.rb
CHANGED
data/lib/camille.rb
CHANGED
@@ -15,10 +15,15 @@ require_relative "camille/types/undefined"
|
|
15
15
|
require_relative "camille/types/union"
|
16
16
|
require_relative "camille/types/tuple"
|
17
17
|
require_relative "camille/types/any"
|
18
|
+
require_relative "camille/types/number_literal"
|
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"
|
18
23
|
require_relative "camille/type"
|
19
24
|
require_relative "camille/type_error"
|
20
25
|
require_relative "camille/type_error_printer"
|
21
|
-
require_relative "camille/
|
26
|
+
require_relative "camille/syntax"
|
22
27
|
require_relative "camille/endpoint"
|
23
28
|
require_relative "camille/schema"
|
24
29
|
require_relative "camille/schemas"
|
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
|
+
version: 0.4.1
|
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-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -38,33 +38,36 @@ 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
|
52
|
-
- lib/camille/core_ext.rb
|
53
55
|
- lib/camille/endpoint.rb
|
54
56
|
- lib/camille/generators/install_generator.rb
|
55
57
|
- lib/camille/generators/schema_generator.rb
|
58
|
+
- lib/camille/generators/templates/.keep
|
56
59
|
- lib/camille/generators/templates/configuration.rb
|
57
|
-
- lib/camille/generators/templates/schema_example.rb
|
58
60
|
- lib/camille/generators/templates/schema_template.erb
|
59
|
-
- lib/camille/generators/templates/type_example.rb
|
60
61
|
- lib/camille/generators/templates/type_template.erb
|
61
62
|
- lib/camille/generators/type_generator.rb
|
62
63
|
- lib/camille/line.rb
|
63
64
|
- lib/camille/loader.rb
|
64
65
|
- lib/camille/main_controller.rb
|
66
|
+
- lib/camille/pick_and_omit.rb
|
65
67
|
- lib/camille/railtie.rb
|
66
68
|
- lib/camille/schema.rb
|
67
69
|
- lib/camille/schemas.rb
|
70
|
+
- lib/camille/syntax.rb
|
68
71
|
- lib/camille/type.rb
|
69
72
|
- lib/camille/type_error.rb
|
70
73
|
- lib/camille/type_error_printer.rb
|
@@ -74,8 +77,12 @@ files:
|
|
74
77
|
- lib/camille/types/boolean.rb
|
75
78
|
- lib/camille/types/null.rb
|
76
79
|
- lib/camille/types/number.rb
|
80
|
+
- lib/camille/types/number_literal.rb
|
77
81
|
- lib/camille/types/object.rb
|
82
|
+
- lib/camille/types/omit.rb
|
83
|
+
- lib/camille/types/pick.rb
|
78
84
|
- lib/camille/types/string.rb
|
85
|
+
- lib/camille/types/string_literal.rb
|
79
86
|
- lib/camille/types/tuple.rb
|
80
87
|
- lib/camille/types/undefined.rb
|
81
88
|
- lib/camille/types/union.rb
|
data/lib/camille/core_ext.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
module Camille
|
2
|
-
module CoreExt
|
3
|
-
module NULL_VALUE; end
|
4
|
-
|
5
|
-
refine ::Hash do
|
6
|
-
def [] key = NULL_VALUE
|
7
|
-
if key == NULL_VALUE
|
8
|
-
Camille::Types::Object.new(self)[]
|
9
|
-
else
|
10
|
-
super
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def | other
|
15
|
-
Camille::Types::Union.new(Camille::Types::Object.new(self), other)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
refine ::Array do
|
20
|
-
def [] key = NULL_VALUE
|
21
|
-
if key == NULL_VALUE
|
22
|
-
Camille::Types::Tuple.new(self)[]
|
23
|
-
else
|
24
|
-
super
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def | other
|
29
|
-
Camille::Types::Union.new(Camille::Types::Tuple.new(self), other)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
using Camille::CoreExt
|
2
|
-
|
3
|
-
class Camille::Schemas::Examples < Camille::Schema
|
4
|
-
include Camille::Types
|
5
|
-
|
6
|
-
get :find do
|
7
|
-
params(
|
8
|
-
id: Number
|
9
|
-
)
|
10
|
-
|
11
|
-
response(
|
12
|
-
example?: Example
|
13
|
-
)
|
14
|
-
end
|
15
|
-
|
16
|
-
get :list do
|
17
|
-
response(
|
18
|
-
examples: Example[]
|
19
|
-
)
|
20
|
-
end
|
21
|
-
|
22
|
-
post :update do
|
23
|
-
params(
|
24
|
-
id: Number,
|
25
|
-
example: Example
|
26
|
-
)
|
27
|
-
|
28
|
-
response(
|
29
|
-
success: Boolean,
|
30
|
-
errors: {
|
31
|
-
message: String
|
32
|
-
}[]
|
33
|
-
)
|
34
|
-
end
|
35
|
-
end
|