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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f5911a8f4d1519a2a373ebee118bce4f110d64f5eaeafece5c8c8e46580b5c20
4
- data.tar.gz: b59a4600bd145350d1217219b0074ffb6e80d8cdc97318995b6ba2b0bffe7450
3
+ metadata.gz: 1d5aa31349909cebc62309be91631d632419b925d9a6ded594485dfa204e9170
4
+ data.tar.gz: d1b1cb24a630a0c109fc1042636372cf193dd135f1ef17784f3907c1a169502e
5
5
  SHA512:
6
- metadata.gz: 3088068a948ca92e1a6bc90690c5f27d5e2c788c3f76e2ba40b5ebf62b7c3fdffac809411941a3f05a77ace636ce16bf7742c12c1ae5700c4b45d7bfb9fdddfb
7
- data.tar.gz: c6f0a0b72216805bf591bc4289172e69382f702ccc14d161a3b63267dc83816a47045e9d61884029349543aac5ecf5aeb43bbc82f0c95ef23ff7df569037b8b6
6
+ metadata.gz: 7d65f32be6f7d8beee2d22b9d5cf5ed4fc84fa91c925605c6aa0be09d51475211ddbee5e23f321892d0f46fa59878af2d9753e3daac7c1e62f2ccf285e8147b6
7
+ data.tar.gz: f2195c3c54f6c571abeb2fa3b541160f734c1ecee6adfaec4bc4025ad0be820ae4235028a3c91427aec3c7cbdcc4ba19ba5f7a3e7cb530a3e943913b57955a78
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- camille (0.1.0)
4
+ camille (0.4.0)
5
5
  rails (>= 6.1, < 8)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -1,8 +1,31 @@
1
1
  # Camille
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/camille`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ## Why?
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
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
- $ bundle install
40
+ ```bash
41
+ bundle install
42
+ bundle exec rails g camille:install
43
+ ```
18
44
 
19
- Or install it yourself as:
45
+ ## Usage
20
46
 
21
- $ gem install camille
47
+ ### Schemas
22
48
 
23
- ## Usage
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
- TODO: Write usage instructions here
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
- ## Development
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
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
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
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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/[USERNAME]/camille.
244
+ Bug reports and pull requests are welcome on GitHub at https://github.com/onyxblade/camille.
@@ -24,13 +24,18 @@ module Camille
24
24
  end
25
25
 
26
26
  def self.instance value
27
- if value.is_a? ::Hash
27
+ case
28
+ when value.is_a?(::Hash)
28
29
  Camille::Types::Object.new(value)
29
- elsif value.is_a? ::Array
30
+ when value.is_a?(::Array)
30
31
  Camille::Types::Tuple.new(value)
31
- elsif value.is_a? Camille::BasicType
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
- elsif value.is_a?(Class) && value < Camille::BasicType && value.directly_instantiable?
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
- Camille::TypeErrorPrinter.new(error).print
21
- raise TypeError.new("Type check failed for response.")
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,6 @@
1
+ Camille.configure do |config|
2
+ # Contents to be placed at the beginning of the output TypeScript file.
3
+ config.ts_header = <<~EOF
4
+ import request from './request'
5
+ EOF
6
+ end
@@ -0,0 +1,14 @@
1
+ using Camille::Syntax
2
+
3
+ class Camille::Schemas::<%= class_name %> < Camille::Schema
4
+ include Camille::Types
5
+
6
+ # get :data do
7
+ # params(
8
+ # id: Number
9
+ # )
10
+ # response(
11
+ # name: String
12
+ # )
13
+ # end
14
+ end
@@ -0,0 +1,9 @@
1
+ using Camille::Syntax
2
+
3
+ class Camille::Types::<%= class_name %> < Camille::Type
4
+ include Camille::Types
5
+
6
+ # alias_of(
7
+ # id: Number
8
+ # )
9
+ end
@@ -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
@@ -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
- loader.push_dir "#{app.root}/config/camille/types", namespace: Camille::Types
14
- loader.push_dir "#{app.root}/config/camille/schemas", namespace: Camille::Schemas
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
- def construct_controller_name_to_schema_map
73
- synchronize do
74
- controller_name_to_schema_map.clear
75
- loaded_schemas.each do |schema|
76
- controller_class_name = "#{schema.klass_name}Controller"
77
- controller_name_to_schema_map[controller_class_name] = schema
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
- def reload
83
- synchronize do
84
- reload_types_and_schemas
85
- Rails.application.reload_routes!
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
- if Rails.env.development?
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
@@ -10,14 +10,17 @@ module Camille
10
10
  Camille::Loader.setup_zeitwerk_loader(app)
11
11
 
12
12
  app.routes.prepend do
13
- get '/camille/endpoints.ts' => 'camille/main#endpoints_ts'
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.reload
23
+ Camille::Loader.reload_types_and_schemas
21
24
  end
22
25
 
23
26
  app.reloaders << update_checker
@@ -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 ' ' * indentation + "#{key}: #{error.message}"
21
+ io.puts "\u00A0" * indentation + "#{key}: #{error.message}"
22
22
  else
23
- io.puts ' ' * indentation + "#{key}:"
23
+ io.puts "\u00A0" * indentation + "#{key}:"
24
24
  print_composite_error io, error, indentation + 2
25
25
  end
26
26
  end
@@ -4,7 +4,7 @@ module Camille
4
4
 
5
5
  def check value
6
6
  unless value.is_a?(Integer) || value.is_a?(Float)
7
- Camille::TypeError.new("Expected number, got #{value.inspect}.")
7
+ Camille::TypeError.new("Expected an integer or a float, got #{value.inspect}.")
8
8
  end
9
9
  end
10
10
 
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Camille
4
- VERSION = "0.2.0"
4
+ VERSION = "0.4.0"
5
5
  end
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/core_ext"
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.2.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-14 00:00:00.000000000 Z
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
@@ -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