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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5779c76d0ca3c695e1e15d0870d94be5739449f7ad1b9422ab9944d139f3a6d1
4
- data.tar.gz: 855906b19324060e4fb8578451e1fd77f692918f6722a81b9f3fbdc5cbf48fd3
3
+ metadata.gz: 2370ceb41fe8fdd701b99671f1d4d82678fc103218e96a10a52e60b9b2d1ad1f
4
+ data.tar.gz: ef8fe63ad679a1744fdef8c58971148d3642be0be2c5e23ff53b510dfef2b449
5
5
  SHA512:
6
- metadata.gz: e555aebf740ecd06dcf96928a3aeafe414430a7d439a83679da90d2e60e23a97cd28bc583a473ddab76c60275e31668b76f9220ca2278c21ecceb8f2f11743fa
7
- data.tar.gz: 00bf823aa9f394c1198cd492f42cd716b8bd59c08fe7e704df64aa92cdb927198ad87761f867de9b59153cba96490190292296755ef1642bd9c759820b00ecc0
6
+ metadata.gz: 6b8068692bd238a82cded511bad5e15d3f0993640a487b335cfd7d1592fe3c8cb45f88924753a1b580ae7448a59d58e423c29332842fa9be09472585bc810ea7
7
+ data.tar.gz: 71bc242a3a3fc9224c0d2e7d3598d9e4be378ff7b2a028e02b55833927cfb516d6309c44bbc7b2296b0b9b780ffe07458435e40a89a1953d6044a465621e7ef9
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## 0.4.1
4
+
5
+ ### Added
6
+
7
+ * Added utility types Pick and Omit
8
+
9
+ ### Fixed
10
+
11
+ * Prevent loader errors from being silently rescued by Rails in development environment
12
+
13
+ ## 0.4.0
14
+
15
+ The first stable release
data/Gemfile CHANGED
@@ -9,5 +9,4 @@ gem "rake", "~> 13.0"
9
9
 
10
10
  gem "rspec", "~> 3.0"
11
11
 
12
- gem "rails"
13
12
  gem "rspec-rails"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- camille (0.3.0)
4
+ camille (0.4.1)
5
5
  rails (>= 6.1, < 8)
6
6
 
7
7
  GEM
@@ -172,7 +172,6 @@ PLATFORMS
172
172
 
173
173
  DEPENDENCIES
174
174
  camille!
175
- rails
176
175
  rake (~> 13.0)
177
176
  rspec (~> 3.0)
178
177
  rspec-rails
data/README.md CHANGED
@@ -1,8 +1,33 @@
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.
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
- $ bundle install
42
+ ```bash
43
+ bundle install
44
+ bundle exec rails g camille:install
45
+ ```
18
46
 
19
- Or install it yourself as:
47
+ ## Usage
20
48
 
21
- $ gem install camille
49
+ ### Schemas
22
50
 
23
- ## Usage
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
- TODO: Write usage instructions here
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
- ## Development
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
- 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.
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
- 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).
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/[USERNAME]/camille.
253
+ Bug reports and pull requests are welcome on GitHub at https://github.com/onyxblade/camille.
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '..'
4
+
5
+ gem "rake", "~> 13.0"
6
+ gem "rspec", "~> 3.0"
7
+ gem "rspec-rails"
8
+
9
+ gem 'rails', '~> 6.1.0'
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '..'
4
+
5
+ gem "rake", "~> 13.0"
6
+ gem "rspec", "~> 3.0"
7
+ gem "rspec-rails"
8
+
9
+ gem 'rails', '~> 7.0.0'
@@ -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)}
@@ -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 copy_type_example
14
- copy_file "type_example.rb", "config/camille/types/example.rb"
13
+ def create_types_folder
14
+ copy_file ".keep", "config/camille/types/.keep"
15
15
  end
16
16
 
17
- def copy_schema_example
18
- copy_file "schema_example.rb", "config/camille/schemas/examples.rb"
17
+ def create_schemas_folder
18
+ copy_file ".keep", "config/camille/schemas/.keep"
19
19
  end
20
20
 
21
21
  end
File without changes
@@ -1,4 +1,4 @@
1
- using Camille::CoreExt
1
+ using Camille::Syntax
2
2
 
3
3
  class Camille::Schemas::<%= class_name %> < Camille::Schema
4
4
  include Camille::Types
@@ -1,9 +1,9 @@
1
- using Camille::CoreExt
1
+ using Camille::Syntax
2
2
 
3
3
  class Camille::Types::<%= class_name %> < Camille::Type
4
4
  include Camille::Types
5
5
 
6
- alias_of(
7
- # id: Number
8
- )
6
+ # alias_of(
7
+ # id: Number
8
+ # )
9
9
  end
@@ -41,11 +41,23 @@ module Camille
41
41
 
42
42
  def reload_types_and_schemas
43
43
  synchronize do
44
- Camille::Loader.loaded_types.clear
45
- Camille::Loader.loaded_schemas.clear
46
- @zeitwerk_loader.reload
47
- eager_load
48
- construct_controller_name_to_schema_map
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
 
@@ -3,6 +3,7 @@ require 'action_controller'
3
3
  module Camille
4
4
  class MainController < ActionController::Base
5
5
  def endpoints_ts
6
+ Camille::Loader.check_and_raise_exception
6
7
  render plain: Camille::CodeGenerator.generate_ts
7
8
  end
8
9
  end
@@ -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 ' ' * 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,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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Camille
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.1"
5
5
  end
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/core_ext"
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.3.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-16 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
@@ -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
@@ -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
@@ -1,10 +0,0 @@
1
- using Camille::CoreExt
2
-
3
- class Camille::Types::Example < Camille::Type
4
- include Camille::Types
5
-
6
- alias_of(
7
- id: Number,
8
- name: String,
9
- )
10
- end