camille 0.2.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +220 -11
- data/lib/camille/basic_type.rb +9 -4
- data/lib/camille/controller_extension.rb +3 -2
- data/lib/camille/generators/install_generator.rb +23 -0
- data/lib/camille/generators/schema_generator.rb +14 -0
- data/lib/camille/generators/templates/.keep +0 -0
- data/lib/camille/generators/templates/configuration.rb +6 -0
- data/lib/camille/generators/templates/schema_template.erb +14 -0
- data/lib/camille/generators/templates/type_template.erb +9 -0
- data/lib/camille/generators/type_generator.rb +14 -0
- data/lib/camille/loader.rb +41 -25
- data/lib/camille/main_controller.rb +1 -5
- data/lib/camille/railtie.rb +5 -2
- data/lib/camille/schema.rb +0 -10
- data/lib/camille/syntax.rb +75 -0
- data/lib/camille/type.rb +0 -9
- data/lib/camille/type_error_printer.rb +2 -2
- data/lib/camille/types/number.rb +1 -1
- data/lib/camille/types/number_literal.rb +25 -0
- data/lib/camille/types/string_literal.rb +25 -0
- data/lib/camille/version.rb +1 -1
- data/lib/camille.rb +8 -1
- metadata +12 -3
- data/lib/camille/core_ext.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d5aa31349909cebc62309be91631d632419b925d9a6ded594485dfa204e9170
|
4
|
+
data.tar.gz: d1b1cb24a630a0c109fc1042636372cf193dd135f1ef17784f3907c1a169502e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d65f32be6f7d8beee2d22b9d5cf5ed4fc84fa91c925605c6aa0be09d51475211ddbee5e23f321892d0f46fa59878af2d9753e3daac7c1e62f2ccf285e8147b6
|
7
|
+
data.tar.gz: f2195c3c54f6c571abeb2fa3b541160f734c1ecee6adfaec4bc4025ad0be820ae4235028a3c91427aec3c7cbdcc4ba19ba5f7a3e7cb530a3e943913b57955a78
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,31 @@
|
|
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. 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
|
+
|
7
|
+
For example, an endpoint defined in Ruby, where `data` is a controller action,
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
get :data do
|
11
|
+
params(
|
12
|
+
id: Number
|
13
|
+
)
|
14
|
+
response(
|
15
|
+
name: String
|
16
|
+
)
|
17
|
+
end
|
18
|
+
```
|
19
|
+
|
20
|
+
will become a function in TypeScript:
|
21
|
+
|
22
|
+
```typescript
|
23
|
+
data(params: {id: number}): Promise<{name: string}>
|
24
|
+
```
|
25
|
+
|
26
|
+
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.
|
27
|
+
|
28
|
+
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
29
|
|
7
30
|
## Installation
|
8
31
|
|
@@ -14,22 +37,208 @@ gem 'camille'
|
|
14
37
|
|
15
38
|
And then execute:
|
16
39
|
|
17
|
-
|
40
|
+
```bash
|
41
|
+
bundle install
|
42
|
+
bundle exec rails g camille:install
|
43
|
+
```
|
18
44
|
|
19
|
-
|
45
|
+
## Usage
|
20
46
|
|
21
|
-
|
47
|
+
### Schemas
|
22
48
|
|
23
|
-
|
49
|
+
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
50
|
|
25
|
-
|
51
|
+
```bash
|
52
|
+
# to generate a schema for ProductsController
|
53
|
+
bundle exec rails g camille:schema products
|
54
|
+
# to generate a schema for Api::ProductController
|
55
|
+
bundle exec rails g camille:schema api/products
|
56
|
+
```
|
26
57
|
|
27
|
-
|
58
|
+
An example of schema definition:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
using Camille::Syntax
|
62
|
+
|
63
|
+
class Camille::Schemas::Api::Products < Camille::Schema
|
64
|
+
include Camille::Types
|
65
|
+
|
66
|
+
get :data do
|
67
|
+
params(
|
68
|
+
id: Number
|
69
|
+
)
|
70
|
+
response(
|
71
|
+
name: String
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
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.
|
78
|
+
|
79
|
+
When generating TypeScript request functions, the `data` endpoint will become a function having the following signature:
|
80
|
+
|
81
|
+
```typescript
|
82
|
+
data(params: {id: number}): Promise<{name: string}>
|
83
|
+
```
|
84
|
+
|
85
|
+
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
|
+
|
87
|
+
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
|
+
|
89
|
+
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.
|
90
|
+
|
91
|
+
### Custom types
|
92
|
+
|
93
|
+
In addition to primitive types, you can define custom types in Camille. The following commands will generate type definition files in `config/camille/types`.
|
94
|
+
|
95
|
+
```bash
|
96
|
+
# to generate a type named Product
|
97
|
+
rails g camille:type product
|
98
|
+
# to generate a type named Nested::Product
|
99
|
+
rails g camille:type nested/product
|
100
|
+
```
|
101
|
+
|
102
|
+
An example of custom type definition:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
using Camille::Syntax
|
106
|
+
|
107
|
+
class Camille::Types::Product < Camille::Type
|
108
|
+
include Camille::Types
|
109
|
+
|
110
|
+
alias_of(
|
111
|
+
id: Number,
|
112
|
+
name: String
|
113
|
+
)
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
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:
|
118
|
+
|
119
|
+
```typescript
|
120
|
+
type Product = {id: number, name: string}
|
121
|
+
```
|
28
122
|
|
29
|
-
|
123
|
+
### Available syntax for types
|
124
|
+
|
125
|
+
Camille supports most of the type syntax in TypeScript. Below is a list of types that you can use in type and schema definition.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
params(
|
129
|
+
# primitive types in TypeScript
|
130
|
+
number: Number,
|
131
|
+
string: String,
|
132
|
+
boolean: Boolean,
|
133
|
+
null: Null,
|
134
|
+
undefined: Undefined,
|
135
|
+
any: Any,
|
136
|
+
# an array type is a type name followed by '[]'
|
137
|
+
array: Number[],
|
138
|
+
# an object type looks like hash
|
139
|
+
object: {
|
140
|
+
field: Number
|
141
|
+
},
|
142
|
+
# an array of objects also works
|
143
|
+
object_array: {
|
144
|
+
field: Number
|
145
|
+
}[]
|
146
|
+
# a union type is two types connected by '|'
|
147
|
+
union: Number | String,
|
148
|
+
# a tuple type is several types put inside '[]'
|
149
|
+
tuple: [Number, String, Boolean],
|
150
|
+
# a field followed by '?' is optional, the same as in TypeScript
|
151
|
+
optional?: Number,
|
152
|
+
# literal types
|
153
|
+
number_literal: 1,
|
154
|
+
string_literal: 'hello',
|
155
|
+
# a custom type we defined above
|
156
|
+
product: Product
|
157
|
+
)
|
158
|
+
```
|
159
|
+
|
160
|
+
String literal types and probably enums are planned for the future.
|
161
|
+
|
162
|
+
### TypeScript generation
|
163
|
+
|
164
|
+
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.
|
165
|
+
|
166
|
+
An example from our previously defined type and schema will be:
|
167
|
+
|
168
|
+
```typescript
|
169
|
+
import request from './request'
|
170
|
+
|
171
|
+
export type Product = {id: number, name: string}
|
172
|
+
|
173
|
+
export default {
|
174
|
+
api: {
|
175
|
+
data(params: {id: number}): Promise<{name: string}> {
|
176
|
+
return request('get', '/api/products/data', params)
|
177
|
+
}
|
178
|
+
}
|
179
|
+
}
|
180
|
+
```
|
181
|
+
|
182
|
+
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.
|
183
|
+
|
184
|
+
### Conversion between camelCase and snake_case
|
185
|
+
|
186
|
+
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.
|
187
|
+
|
188
|
+
For example,
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
get :special_data do
|
192
|
+
params(
|
193
|
+
long_id: Number
|
194
|
+
)
|
195
|
+
response(
|
196
|
+
long_name: String
|
197
|
+
)
|
198
|
+
end
|
199
|
+
```
|
200
|
+
|
201
|
+
will have TS signature:
|
202
|
+
|
203
|
+
```typescript
|
204
|
+
specialData(params: {longId: number}): Promise<{longName: string}>
|
205
|
+
```
|
206
|
+
|
207
|
+
In the Rails action you still use `params[:long_id]` to access the parameter and return `long_name` in response.
|
208
|
+
|
209
|
+
### Typechecking
|
210
|
+
|
211
|
+
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.
|
212
|
+
|
213
|
+
For example for
|
214
|
+
```ruby
|
215
|
+
response(
|
216
|
+
object: {
|
217
|
+
array: Number[]
|
218
|
+
}
|
219
|
+
)
|
220
|
+
```
|
221
|
+
|
222
|
+
if we return such a JSON in our action
|
223
|
+
```ruby
|
224
|
+
render json: {
|
225
|
+
object: {
|
226
|
+
array: [1, 2, '3']
|
227
|
+
}
|
228
|
+
}
|
229
|
+
```
|
230
|
+
|
231
|
+
Camille will print the following error:
|
232
|
+
```
|
233
|
+
object:
|
234
|
+
array:
|
235
|
+
[2]: Expected number, got "3".
|
236
|
+
```
|
237
|
+
|
238
|
+
## Development
|
30
239
|
|
31
|
-
|
240
|
+
Run tests with `bundle exec rake`.
|
32
241
|
|
33
242
|
## Contributing
|
34
243
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
244
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/onyxblade/camille.
|
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)}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
|
2
|
+
module Camille
|
3
|
+
module Generators
|
4
|
+
class InstallGenerator < Rails::Generators::Base
|
5
|
+
source_root File.expand_path("templates", __dir__)
|
6
|
+
|
7
|
+
desc 'install camille'
|
8
|
+
|
9
|
+
def copy_configuration_file
|
10
|
+
copy_file "configuration.rb", "config/camille/configuration.rb"
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_types_folder
|
14
|
+
copy_file ".keep", "config/camille/types/.keep"
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_schemas_folder
|
18
|
+
copy_file ".keep", "config/camille/schemas/.keep"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
module Camille
|
3
|
+
module Generators
|
4
|
+
class SchemaGenerator < Rails::Generators::NamedBase
|
5
|
+
source_root File.expand_path("templates", __dir__)
|
6
|
+
|
7
|
+
desc 'generate camille schema'
|
8
|
+
|
9
|
+
def generate_type_file
|
10
|
+
template 'schema_template.erb', "config/camille/schemas/#{@name}.rb"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
File without changes
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
module Camille
|
3
|
+
module Generators
|
4
|
+
class TypeGenerator < Rails::Generators::NamedBase
|
5
|
+
source_root File.expand_path("templates", __dir__)
|
6
|
+
|
7
|
+
desc 'generate camille type'
|
8
|
+
|
9
|
+
def generate_type_file
|
10
|
+
template 'type_template.erb', "config/camille/types/#{@name}.rb"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/camille/loader.rb
CHANGED
@@ -10,8 +10,25 @@ module Camille
|
|
10
10
|
synchronize do
|
11
11
|
loader = Zeitwerk::Loader.new
|
12
12
|
loader.enable_reloading if !app.config.cache_classes
|
13
|
-
|
14
|
-
|
13
|
+
|
14
|
+
types_dir = "#{app.root}/config/camille/types"
|
15
|
+
schemas_dir = "#{app.root}/config/camille/schemas"
|
16
|
+
|
17
|
+
if Dir.exist? types_dir
|
18
|
+
loader.push_dir types_dir, namespace: Camille::Types
|
19
|
+
else
|
20
|
+
unless inside_generator?
|
21
|
+
puts "[Camille Warning] Expected folder `config/camille/types`. Run `rails g camille:install` to fix it."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
if Dir.exist? schemas_dir
|
26
|
+
loader.push_dir schemas_dir, namespace: Camille::Schemas
|
27
|
+
else
|
28
|
+
unless inside_generator?
|
29
|
+
puts "[Camille Warning] Expected folder `config/camille/schemas`. Run `rails g camille:install` to fix it."
|
30
|
+
end
|
31
|
+
end
|
15
32
|
|
16
33
|
loader.setup
|
17
34
|
@zeitwerk_loader = loader
|
@@ -22,17 +39,6 @@ module Camille
|
|
22
39
|
end
|
23
40
|
end
|
24
41
|
|
25
|
-
def eager_load
|
26
|
-
@eager_loading = true
|
27
|
-
load @configuration_location
|
28
|
-
@zeitwerk_loader.eager_load
|
29
|
-
@eager_loading = false
|
30
|
-
end
|
31
|
-
|
32
|
-
def eager_loading?
|
33
|
-
@eager_loading
|
34
|
-
end
|
35
|
-
|
36
42
|
def reload_types_and_schemas
|
37
43
|
synchronize do
|
38
44
|
Camille::Loader.loaded_types.clear
|
@@ -69,22 +75,32 @@ module Camille
|
|
69
75
|
end
|
70
76
|
end
|
71
77
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
+
private
|
79
|
+
def eager_load
|
80
|
+
if File.exist?(@configuration_location)
|
81
|
+
load @configuration_location
|
82
|
+
else
|
83
|
+
unless inside_generator?
|
84
|
+
puts "[Camille Warning] Expected file `config/camille/configuration.rb`. Run `rails g camille:install` to fix it."
|
85
|
+
end
|
78
86
|
end
|
87
|
+
@zeitwerk_loader.eager_load
|
79
88
|
end
|
80
|
-
end
|
81
89
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
90
|
+
def construct_controller_name_to_schema_map
|
91
|
+
synchronize do
|
92
|
+
controller_name_to_schema_map.clear
|
93
|
+
loaded_schemas.each do |schema|
|
94
|
+
controller_class_name = "#{schema.klass_name}Controller"
|
95
|
+
controller_name_to_schema_map[controller_class_name] = schema
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def inside_generator?
|
101
|
+
# https://stackoverflow.com/a/53963584
|
102
|
+
!(Rails.const_defined?(:Server) || Rails.const_defined?(:Console))
|
86
103
|
end
|
87
|
-
end
|
88
104
|
|
89
105
|
end
|
90
106
|
|
@@ -3,11 +3,7 @@ require 'action_controller'
|
|
3
3
|
module Camille
|
4
4
|
class MainController < ActionController::Base
|
5
5
|
def endpoints_ts
|
6
|
-
|
7
|
-
render plain: Camille::CodeGenerator.generate_ts
|
8
|
-
else
|
9
|
-
head 404
|
10
|
-
end
|
6
|
+
render plain: Camille::CodeGenerator.generate_ts
|
11
7
|
end
|
12
8
|
end
|
13
9
|
end
|
data/lib/camille/railtie.rb
CHANGED
@@ -10,14 +10,17 @@ module Camille
|
|
10
10
|
Camille::Loader.setup_zeitwerk_loader(app)
|
11
11
|
|
12
12
|
app.routes.prepend do
|
13
|
-
|
13
|
+
if Rails.env.development?
|
14
|
+
get '/camille/endpoints.ts' => 'camille/main#endpoints_ts'
|
15
|
+
end
|
16
|
+
|
14
17
|
Camille::Loader.register_routes(self)
|
15
18
|
end
|
16
19
|
|
17
20
|
dir = "#{Rails.root}/config/camille"
|
18
21
|
|
19
22
|
update_checker = ActiveSupport::FileUpdateChecker.new([], {dir => ['rb']}) do
|
20
|
-
Camille::Loader.
|
23
|
+
Camille::Loader.reload_types_and_schemas
|
21
24
|
end
|
22
25
|
|
23
26
|
app.reloaders << update_checker
|
data/lib/camille/schema.rb
CHANGED
@@ -2,20 +2,10 @@ module Camille
|
|
2
2
|
class Schema
|
3
3
|
class AlreadyDefinedError < ::ArgumentError; end
|
4
4
|
|
5
|
-
include Camille::Types
|
6
|
-
|
7
5
|
def self.endpoints
|
8
6
|
@endpoints ||= {}
|
9
7
|
end
|
10
8
|
|
11
|
-
def self.const_missing name
|
12
|
-
if Camille::Loader.eager_loading?
|
13
|
-
Camille::Types.const_get(name)
|
14
|
-
else
|
15
|
-
super
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
9
|
def self.path
|
20
10
|
"/#{ActiveSupport::Inflector.underscore klass_name}"
|
21
11
|
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
|
data/lib/camille/type.rb
CHANGED
@@ -2,7 +2,6 @@ module Camille
|
|
2
2
|
# This class represents all custom types defined by the user.
|
3
3
|
class Type < BasicType
|
4
4
|
class NotImplementedError < ::NotImplementedError; end
|
5
|
-
include Camille::Types
|
6
5
|
|
7
6
|
attr_reader :underlying
|
8
7
|
|
@@ -30,14 +29,6 @@ module Camille
|
|
30
29
|
self.class.klass_name.gsub(/::/, '_')
|
31
30
|
end
|
32
31
|
|
33
|
-
def self.const_missing name
|
34
|
-
if Camille::Loader.eager_loading?
|
35
|
-
Camille::Types.const_get(name)
|
36
|
-
else
|
37
|
-
super
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
32
|
def self.inherited klass
|
42
33
|
Camille::Loader.loaded_types << klass
|
43
34
|
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,25 @@
|
|
1
|
+
module Camille
|
2
|
+
module Types
|
3
|
+
class NumberLiteral < Camille::BasicType
|
4
|
+
class ArgumentError < ::ArgumentError; end
|
5
|
+
|
6
|
+
def initialize value
|
7
|
+
if value.is_a?(Integer) || value.is_a?(Float)
|
8
|
+
@value = value
|
9
|
+
else
|
10
|
+
raise ArgumentError.new("Expecting an integer or a float, got #{value.inspect}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def check value
|
15
|
+
unless value == @value
|
16
|
+
Camille::TypeError.new("Expected number literal #{@value.inspect}, got #{value.inspect}.")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def literal
|
21
|
+
@value.to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Camille
|
2
|
+
module Types
|
3
|
+
class StringLiteral < Camille::BasicType
|
4
|
+
class ArgumentError < ::ArgumentError; end
|
5
|
+
|
6
|
+
def initialize value
|
7
|
+
if value.is_a?(::String)
|
8
|
+
@value = value
|
9
|
+
else
|
10
|
+
raise ArgumentError.new("Expecting a string, got #{value.inspect}")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def check value
|
15
|
+
unless value == @value
|
16
|
+
Camille::TypeError.new("Expected string literal #{@value.inspect}, got #{value.inspect}.")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def literal
|
21
|
+
@value.inspect
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/camille/version.rb
CHANGED
data/lib/camille.rb
CHANGED
@@ -15,10 +15,12 @@ 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"
|
18
20
|
require_relative "camille/type"
|
19
21
|
require_relative "camille/type_error"
|
20
22
|
require_relative "camille/type_error_printer"
|
21
|
-
require_relative "camille/
|
23
|
+
require_relative "camille/syntax"
|
22
24
|
require_relative "camille/endpoint"
|
23
25
|
require_relative "camille/schema"
|
24
26
|
require_relative "camille/schemas"
|
@@ -30,6 +32,11 @@ require_relative "camille/configuration"
|
|
30
32
|
require_relative "camille/code_generator"
|
31
33
|
require_relative "camille/main_controller"
|
32
34
|
|
35
|
+
require "rails/generators"
|
36
|
+
require_relative "camille/generators/install_generator"
|
37
|
+
require_relative "camille/generators/type_generator"
|
38
|
+
require_relative "camille/generators/schema_generator"
|
39
|
+
|
33
40
|
module Camille
|
34
41
|
class Error < StandardError; end
|
35
42
|
|
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.0
|
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
|
@@ -49,14 +49,21 @@ files:
|
|
49
49
|
- lib/camille/code_generator.rb
|
50
50
|
- lib/camille/configuration.rb
|
51
51
|
- lib/camille/controller_extension.rb
|
52
|
-
- lib/camille/core_ext.rb
|
53
52
|
- lib/camille/endpoint.rb
|
53
|
+
- lib/camille/generators/install_generator.rb
|
54
|
+
- lib/camille/generators/schema_generator.rb
|
55
|
+
- lib/camille/generators/templates/.keep
|
56
|
+
- lib/camille/generators/templates/configuration.rb
|
57
|
+
- lib/camille/generators/templates/schema_template.erb
|
58
|
+
- lib/camille/generators/templates/type_template.erb
|
59
|
+
- lib/camille/generators/type_generator.rb
|
54
60
|
- lib/camille/line.rb
|
55
61
|
- lib/camille/loader.rb
|
56
62
|
- lib/camille/main_controller.rb
|
57
63
|
- lib/camille/railtie.rb
|
58
64
|
- lib/camille/schema.rb
|
59
65
|
- lib/camille/schemas.rb
|
66
|
+
- lib/camille/syntax.rb
|
60
67
|
- lib/camille/type.rb
|
61
68
|
- lib/camille/type_error.rb
|
62
69
|
- lib/camille/type_error_printer.rb
|
@@ -66,8 +73,10 @@ files:
|
|
66
73
|
- lib/camille/types/boolean.rb
|
67
74
|
- lib/camille/types/null.rb
|
68
75
|
- lib/camille/types/number.rb
|
76
|
+
- lib/camille/types/number_literal.rb
|
69
77
|
- lib/camille/types/object.rb
|
70
78
|
- lib/camille/types/string.rb
|
79
|
+
- lib/camille/types/string_literal.rb
|
71
80
|
- lib/camille/types/tuple.rb
|
72
81
|
- lib/camille/types/undefined.rb
|
73
82
|
- 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
|