primate-run 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/primate/pema.rb +134 -0
- data/lib/primate/readable.rb +66 -0
- data/lib/primate/request.rb +20 -0
- data/lib/primate/request_bag.rb +66 -0
- data/lib/primate/request_body.rb +58 -0
- data/lib/primate/response.rb +30 -0
- data/lib/primate/route.rb +104 -0
- data/lib/primate/session.rb +130 -0
- data/lib/primate/type.rb +44 -0
- data/lib/primate/uploaded_file.rb +19 -0
- data/lib/primate/url.rb +24 -0
- data/lib/primate/version.rb +5 -0
- data/lib/primate.rb +11 -0
- data/sig/primate.rbs +162 -0
- metadata +100 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5e3646fb9a37d9abb014fbe158eb36a657c9b2cc981eadc9f9f9688964ba6676
|
|
4
|
+
data.tar.gz: 41f42c7f73ff43165363faf5c1df9431d7e36faf0e2178ebb88dc864cc6d3758
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 72bef66e9e5578b55874dea7f31408813e2c439240a60dd73e20421e1550743440065823d5bf41bd093a5bb5a333a11479a5cfe760ad80c986ae46d03b2dc687
|
|
7
|
+
data.tar.gz: 8c8084f98a4c0151b26506410c8ca4e60c2729e401f259ecf439381f00159d84d89e55efb2da381a3b68bab438e504118f0c8b64f0fe264ad4e13b25ea2c16bb
|
data/lib/primate/pema.rb
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pema
|
|
4
|
+
class ValidationError < StandardError; end
|
|
5
|
+
|
|
6
|
+
class Field
|
|
7
|
+
def parse(value, coerce = false)
|
|
8
|
+
raise NotImplementedError, 'Subclasses must implement parse'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class StringType < Field
|
|
13
|
+
def parse(value, coerce = false)
|
|
14
|
+
return value if value.is_a?(String)
|
|
15
|
+
|
|
16
|
+
if coerce
|
|
17
|
+
return value.to_s
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
raise ValidationError, "expected string, got #{value.class}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class BooleanType < Field
|
|
25
|
+
def parse(value, coerce = false)
|
|
26
|
+
return value if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
27
|
+
|
|
28
|
+
if coerce
|
|
29
|
+
case value
|
|
30
|
+
when String
|
|
31
|
+
return false if value.empty?
|
|
32
|
+
case value.downcase
|
|
33
|
+
when 'true', '1', 'yes', 'on'
|
|
34
|
+
return true
|
|
35
|
+
when 'false', '0', 'no', 'off'
|
|
36
|
+
return false
|
|
37
|
+
else
|
|
38
|
+
raise ValidationError, "cannot parse '#{value}' as boolean"
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
raise ValidationError, "cannot coerce #{value.class} to boolean"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
raise ValidationError, "expected boolean, got #{value.class}"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class IntType < Field
|
|
50
|
+
def parse(value, coerce = false)
|
|
51
|
+
return value if value.is_a?(Integer)
|
|
52
|
+
|
|
53
|
+
if coerce
|
|
54
|
+
case value
|
|
55
|
+
when Float
|
|
56
|
+
return value.to_i
|
|
57
|
+
when String
|
|
58
|
+
return 0 if value.empty?
|
|
59
|
+
return Integer(value)
|
|
60
|
+
else
|
|
61
|
+
raise ValidationError, "cannot coerce #{value.class} to int"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
raise ValidationError, "expected integer, got #{value.class}"
|
|
66
|
+
rescue ArgumentError
|
|
67
|
+
raise ValidationError, "cannot parse '#{value}' as integer"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class FloatType < Field
|
|
72
|
+
def parse(value, coerce = false)
|
|
73
|
+
return value if value.is_a?(Float)
|
|
74
|
+
|
|
75
|
+
if coerce
|
|
76
|
+
case value
|
|
77
|
+
when Integer
|
|
78
|
+
return value.to_f
|
|
79
|
+
when String
|
|
80
|
+
return 0.0 if value.empty?
|
|
81
|
+
return Float(value)
|
|
82
|
+
else
|
|
83
|
+
raise ValidationError, "cannot coerce #{value.class} to float"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
raise ValidationError, "expected float, got #{value.class}"
|
|
88
|
+
rescue ArgumentError
|
|
89
|
+
raise ValidationError, "cannot parse '#{value}' as float"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.string
|
|
94
|
+
StringType.new
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.boolean
|
|
98
|
+
BooleanType.new
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def self.int
|
|
102
|
+
IntType.new
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.float
|
|
106
|
+
FloatType.new
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
class Schema
|
|
110
|
+
def initialize(fields)
|
|
111
|
+
@fields = fields
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def parse(data, coerce = false)
|
|
115
|
+
result = {}
|
|
116
|
+
|
|
117
|
+
@fields.each do |name, field|
|
|
118
|
+
value = data.key?(name) ? data[name] : ''
|
|
119
|
+
|
|
120
|
+
begin
|
|
121
|
+
result[name] = field.parse(value, coerce)
|
|
122
|
+
rescue ValidationError => e
|
|
123
|
+
raise ValidationError, "parsing failed for field '#{name}': #{e.message}"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
result
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.schema(fields)
|
|
132
|
+
Schema.new(fields)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Primate
|
|
4
|
+
class Readable
|
|
5
|
+
attr_reader :content_type
|
|
6
|
+
|
|
7
|
+
def initialize(typed_array, content_type = nil)
|
|
8
|
+
@ta = typed_array
|
|
9
|
+
@content_type = content_type
|
|
10
|
+
@pos = 0
|
|
11
|
+
@size = @ta[:length].to_i
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def size = @size
|
|
15
|
+
def eof? = @pos >= @size
|
|
16
|
+
def rewind; @pos = 0; self; end
|
|
17
|
+
|
|
18
|
+
# return a binary string (ASCII-8BIT), advance position
|
|
19
|
+
def read(n = nil)
|
|
20
|
+
return ''.b if eof?
|
|
21
|
+
n = n ? [n, @size - @pos].min : (@size - @pos)
|
|
22
|
+
str = pack_range(@pos, n)
|
|
23
|
+
@pos += n
|
|
24
|
+
str
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# return an array of bytes (integers), advance position
|
|
28
|
+
def bytes(n = nil)
|
|
29
|
+
return [] if eof?
|
|
30
|
+
n = n ? [n, @size - @pos].min : (@size - @pos)
|
|
31
|
+
arr = Array.new(n) { |i| @ta[@pos + i].to_i }
|
|
32
|
+
@pos += n
|
|
33
|
+
arr
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# look ahead without advancing position (integers)
|
|
37
|
+
def peek(n)
|
|
38
|
+
n = [n, @size - @pos].min
|
|
39
|
+
Array.new(n) { |i| @ta[@pos + i].to_i }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# first n bytes from the beginning (integers), does not affect position
|
|
43
|
+
def head(n = 4)
|
|
44
|
+
n = [n, @size].min
|
|
45
|
+
Array.new(n) { |i| @ta[i].to_i }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# enumerate binary string chunks starting at current position (no rewind)
|
|
49
|
+
def each_chunk(chunk = 64 * 1024)
|
|
50
|
+
return enum_for(:each_chunk, chunk) unless block_given?
|
|
51
|
+
off = @pos
|
|
52
|
+
while off < @size
|
|
53
|
+
n = [chunk, @size - off].min
|
|
54
|
+
yield pack_range(off, n)
|
|
55
|
+
off += n
|
|
56
|
+
end
|
|
57
|
+
self
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def pack_range(off, n)
|
|
63
|
+
Array.new(n) { |i| @ta[off + i].to_i }.pack('C*')
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'url'
|
|
4
|
+
require_relative 'request_bag'
|
|
5
|
+
require_relative 'request_body'
|
|
6
|
+
|
|
7
|
+
class Request
|
|
8
|
+
attr_reader :url, :body, :path, :query, :headers, :cookies
|
|
9
|
+
|
|
10
|
+
# @param request [Object] JavaScript request object from the runtime
|
|
11
|
+
# @param helpers [Object] Helper functions from JavaScript runtime
|
|
12
|
+
def initialize(request, helpers)
|
|
13
|
+
@url = Primate::URL.new(request['url'])
|
|
14
|
+
@body = RequestBody.new(request['body'], helpers)
|
|
15
|
+
@path = RequestBag.new(request['path'], helpers)
|
|
16
|
+
@query = RequestBag.new(request['query'], helpers)
|
|
17
|
+
@headers = RequestBag.new(request['headers'], helpers)
|
|
18
|
+
@cookies = RequestBag.new(request['cookies'], helpers)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'type'
|
|
4
|
+
|
|
5
|
+
class RequestBag
|
|
6
|
+
# initialize with JavaScript object data
|
|
7
|
+
#
|
|
8
|
+
# @param data [Object] JavaScript object from runtime
|
|
9
|
+
def initialize(data, helpers)
|
|
10
|
+
@data = type(data, helpers)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# get a value by key
|
|
14
|
+
#
|
|
15
|
+
# @param key [String] The key to look up
|
|
16
|
+
# @return [String] The value as string
|
|
17
|
+
# @throws RuntimeError if key is missing
|
|
18
|
+
def get(key)
|
|
19
|
+
raise "RequestBag has no key #{key}" unless has?(key)
|
|
20
|
+
@data[key].to_s
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Get a value by key (alias for get, Ruby style)
|
|
24
|
+
def [](key)
|
|
25
|
+
get(key)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Try to get a value by key
|
|
29
|
+
#
|
|
30
|
+
# @param key [String] The key to look up
|
|
31
|
+
# @return [String, nil] The value or nil if absent
|
|
32
|
+
def try(key)
|
|
33
|
+
has?(key) ? @data[key].to_s : nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Whether the bag contains a defined value for the key
|
|
37
|
+
#
|
|
38
|
+
# @param key [String] The key to check
|
|
39
|
+
# @return [Boolean] True if key exists with defined value
|
|
40
|
+
def has?(key)
|
|
41
|
+
@data.key?(key) && !@data[key].nil?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Parse the entire bag with a schema
|
|
45
|
+
#
|
|
46
|
+
# @param schema [Object] Object with parse method
|
|
47
|
+
# @param coerce [Boolean] Whether to coerce types
|
|
48
|
+
# @return [Object] Parsed result
|
|
49
|
+
def parse(schema, coerce = false)
|
|
50
|
+
schema.parse(@data, coerce)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Convert to hash (Ruby standard conversion)
|
|
54
|
+
#
|
|
55
|
+
# @return [Hash] The underlying data
|
|
56
|
+
def to_h
|
|
57
|
+
@data
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Size of the bag
|
|
61
|
+
#
|
|
62
|
+
# @return [Integer] Number of entries
|
|
63
|
+
def size
|
|
64
|
+
@data.size
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require_relative 'readable'
|
|
5
|
+
require_relative 'uploaded_file'
|
|
6
|
+
|
|
7
|
+
# Wrapper for request body with type-specific methods
|
|
8
|
+
class RequestBody
|
|
9
|
+
# Initialize with JavaScript body object
|
|
10
|
+
#
|
|
11
|
+
# @param body [Object] JavaScript body object from runtime
|
|
12
|
+
# @param helpers [Object] Helper functions from JavaScript runtime
|
|
13
|
+
def initialize(body, helpers)
|
|
14
|
+
@body = body
|
|
15
|
+
@helpers = helpers
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Get body as JSON (parsed hash)
|
|
19
|
+
#
|
|
20
|
+
# @return [Hash] Parsed JSON data
|
|
21
|
+
def json
|
|
22
|
+
JSON.parse(@body.json.to_s)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Get body as plain text
|
|
26
|
+
#
|
|
27
|
+
# @return [String] Body as string
|
|
28
|
+
def text
|
|
29
|
+
@body.text.to_s
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Get form fields as hash
|
|
33
|
+
#
|
|
34
|
+
# @return [Hash] Form field data
|
|
35
|
+
def fields
|
|
36
|
+
JSON.parse(@body.fields.to_s)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def files
|
|
40
|
+
files = @body.files
|
|
41
|
+
|
|
42
|
+
Array.new(files[:length].to_i) do |i|
|
|
43
|
+
f = files[i]
|
|
44
|
+
Primate::UploadedFile.new(
|
|
45
|
+
field: f['field'].to_s,
|
|
46
|
+
name: f['name'].to_s,
|
|
47
|
+
type: f['type'].to_s,
|
|
48
|
+
size: f['size'].to_i,
|
|
49
|
+
bytes: f['bytes']
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def binary
|
|
55
|
+
binary = @body.binary
|
|
56
|
+
Primate::Readable.new(binary['buffer'], binary['mime'].to_s)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Response
|
|
4
|
+
# Create a view response
|
|
5
|
+
#
|
|
6
|
+
# @param name [String] The view template name
|
|
7
|
+
# @param props [Hash] Props to pass to the view
|
|
8
|
+
# @param options [Hash] Additional view options
|
|
9
|
+
# @return [Hash] Response object for the Primate framework
|
|
10
|
+
def self.view(name, props = {}, options = {})
|
|
11
|
+
{ __PRMT__: 'view', name: name, props: props, options: options }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Create a redirect response
|
|
15
|
+
#
|
|
16
|
+
# @param location [String] URL to redirect to
|
|
17
|
+
# @param options [Hash] Redirect options (status code, etc.)
|
|
18
|
+
# @return [Hash] Response object for the Primate framework
|
|
19
|
+
def self.redirect(location, options = {})
|
|
20
|
+
{ __PRMT__: 'redirect', location: location, options: options }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Create an error response
|
|
24
|
+
#
|
|
25
|
+
# @param options [Hash] Error options
|
|
26
|
+
# @return [Hash] Response object for the Primate framework
|
|
27
|
+
def self.error(options = {})
|
|
28
|
+
{ __PRMT__: 'error', options: options }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'request'
|
|
4
|
+
require_relative 'response'
|
|
5
|
+
require_relative '../primate'
|
|
6
|
+
|
|
7
|
+
# Route registration and handling
|
|
8
|
+
module Route
|
|
9
|
+
@routes = {}
|
|
10
|
+
|
|
11
|
+
# Register a GET route handler
|
|
12
|
+
#
|
|
13
|
+
# @yieldparam request [Request] The HTTP request object
|
|
14
|
+
# @return [void]
|
|
15
|
+
def self.get(&block)
|
|
16
|
+
@routes['GET'] = block
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Register a POST route handler
|
|
20
|
+
#
|
|
21
|
+
# @yieldparam request [Request] The HTTP request object
|
|
22
|
+
# @return [void]
|
|
23
|
+
def self.post(&block)
|
|
24
|
+
@routes['POST'] = block
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Register a PUT route handler
|
|
28
|
+
#
|
|
29
|
+
# @yieldparam request [Request] The HTTP request object
|
|
30
|
+
# @return [void]
|
|
31
|
+
def self.put(&block)
|
|
32
|
+
@routes['PUT'] = block
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Register a PATCH route handler
|
|
36
|
+
#
|
|
37
|
+
# @yieldparam request [Request] The HTTP request object
|
|
38
|
+
# @return [void]
|
|
39
|
+
def self.patch(&block)
|
|
40
|
+
@routes['PATCH'] = block
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Register a DELETE route handler
|
|
44
|
+
#
|
|
45
|
+
# @yieldparam request [Request] The HTTP request object
|
|
46
|
+
# @return [void]
|
|
47
|
+
def self.delete(&block)
|
|
48
|
+
@routes['DELETE'] = block
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Register a HEAD route handler
|
|
52
|
+
#
|
|
53
|
+
# @yieldparam request [Request] The HTTP request object
|
|
54
|
+
# @return [void]
|
|
55
|
+
def self.head(&block)
|
|
56
|
+
@routes['HEAD'] = block
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Register a OPTIONS route handler
|
|
60
|
+
#
|
|
61
|
+
# @yieldparam request [Request] The HTTP request object
|
|
62
|
+
# @return [void]
|
|
63
|
+
def self.options(&block)
|
|
64
|
+
@routes['OPTIONS'] = block
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Register a CONNECT route handler
|
|
68
|
+
#
|
|
69
|
+
# @yieldparam request [Request] The HTTP request object
|
|
70
|
+
# @return [void]
|
|
71
|
+
def self.connect(&block)
|
|
72
|
+
@routes['CONNECT'] = block
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Register a TRACE route handler
|
|
76
|
+
#
|
|
77
|
+
# @yieldparam request [Request] The HTTP request object
|
|
78
|
+
# @return [void]
|
|
79
|
+
def self.trace(&block)
|
|
80
|
+
@routes['TRACE'] = block
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get all registered routes
|
|
84
|
+
#
|
|
85
|
+
# @return [Hash] Hash of HTTP method => handler block
|
|
86
|
+
def self.routes
|
|
87
|
+
@routes
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def self.set_session(session, helpers)
|
|
91
|
+
PrimateInternal.set_session(session, helpers)
|
|
92
|
+
end
|
|
93
|
+
# Execute a route handler for the given HTTP method
|
|
94
|
+
#
|
|
95
|
+
# @param method [String] HTTP method
|
|
96
|
+
# @param request [Request] Request object
|
|
97
|
+
# @return [Object] Response from the route handler
|
|
98
|
+
def self.call_route(method, request)
|
|
99
|
+
handler = @routes[method.upcase]
|
|
100
|
+
return Response.error(status: 404) unless handler
|
|
101
|
+
|
|
102
|
+
handler.call(request)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Session management helpers
|
|
4
|
+
module Session
|
|
5
|
+
class << self
|
|
6
|
+
attr_accessor :current
|
|
7
|
+
|
|
8
|
+
# Set the current session instance (called by framework)
|
|
9
|
+
#
|
|
10
|
+
# @param session_instance [SessionInstance] Session instance from JavaScript runtime
|
|
11
|
+
# @return [void]
|
|
12
|
+
def set_current(session_instance)
|
|
13
|
+
self.current = session_instance
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Get the session ID
|
|
17
|
+
#
|
|
18
|
+
# @return [String] Session identifier
|
|
19
|
+
def id
|
|
20
|
+
current&.id
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Check if session exists
|
|
24
|
+
#
|
|
25
|
+
# @return [Boolean] True if session exists
|
|
26
|
+
def exists?
|
|
27
|
+
current&.exists || false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Create a new session with data
|
|
31
|
+
#
|
|
32
|
+
# @param data [Hash] Initial session data
|
|
33
|
+
# @return [void]
|
|
34
|
+
def create(data)
|
|
35
|
+
current&.create(data)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Get session data (raises if no session)
|
|
39
|
+
#
|
|
40
|
+
# @return [Hash] Session data
|
|
41
|
+
def get
|
|
42
|
+
current&.get || {}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Try to get session data (returns empty if no session)
|
|
46
|
+
#
|
|
47
|
+
# @return [Hash] Session data or empty hash
|
|
48
|
+
def try
|
|
49
|
+
current&.try || {}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Set session data
|
|
53
|
+
#
|
|
54
|
+
# @param data [Hash] Data to store in session
|
|
55
|
+
# @return [void]
|
|
56
|
+
def set(data)
|
|
57
|
+
current&.set(data)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Destroy the session
|
|
61
|
+
#
|
|
62
|
+
# @return [void]
|
|
63
|
+
def destroy
|
|
64
|
+
current&.destroy
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Internal session instance class (used by framework)
|
|
70
|
+
class SessionInstance
|
|
71
|
+
# Initialize session with JavaScript session object and helpers
|
|
72
|
+
#
|
|
73
|
+
# @param session [Object] JavaScript session object from the runtime
|
|
74
|
+
# @param helpers [Object] Helper functions from JavaScript runtime
|
|
75
|
+
def initialize(session, helpers)
|
|
76
|
+
@session = session
|
|
77
|
+
@helpers = helpers
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Get the session ID
|
|
81
|
+
#
|
|
82
|
+
# @return [String] Session identifier
|
|
83
|
+
def id
|
|
84
|
+
@session['id']
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Check if session exists
|
|
88
|
+
#
|
|
89
|
+
# @return [Boolean] True if session exists
|
|
90
|
+
def exists
|
|
91
|
+
@session['exists']
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Create a new session with data
|
|
95
|
+
#
|
|
96
|
+
# @param data [Hash] Initial session data
|
|
97
|
+
# @return [void]
|
|
98
|
+
def create(data)
|
|
99
|
+
@session.create(data)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Get session data (raises if no session)
|
|
103
|
+
#
|
|
104
|
+
# @return [Hash] Session data
|
|
105
|
+
def get
|
|
106
|
+
@session.get
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Try to get session data (returns empty if no session)
|
|
110
|
+
#
|
|
111
|
+
# @return [Hash] Session data or empty hash
|
|
112
|
+
def try
|
|
113
|
+
@session.try
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Set session data
|
|
117
|
+
#
|
|
118
|
+
# @param data [Hash] Data to store in session
|
|
119
|
+
# @return [void]
|
|
120
|
+
def set(data)
|
|
121
|
+
@session.set(data)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Destroy the session
|
|
125
|
+
#
|
|
126
|
+
# @return [void]
|
|
127
|
+
def destroy
|
|
128
|
+
@session.destroy
|
|
129
|
+
end
|
|
130
|
+
end
|
data/lib/primate/type.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
def type(value, helpers)
|
|
6
|
+
type = helpers.type(value).to_s
|
|
7
|
+
|
|
8
|
+
if type == 'integer'
|
|
9
|
+
return value.to_i
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
if type == 'float'
|
|
13
|
+
return value.to_f
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if type == 'boolean'
|
|
17
|
+
return value == JS::True
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if type == 'string'
|
|
21
|
+
return value.to_s
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
if type == 'nil'
|
|
25
|
+
return nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if type == 'array'
|
|
29
|
+
as_array = JS.global[:Array].from(value)
|
|
30
|
+
return Array.new(as_array[:length].to_i) {
|
|
31
|
+
type(as_array[_1], helpers)
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if type == 'object'
|
|
36
|
+
as_entries = JS.global[:Object].entries(value)
|
|
37
|
+
return Hash[Array.new(as_entries[:length].to_i) {[
|
|
38
|
+
as_entries[_1][0].to_s,
|
|
39
|
+
type(as_entries[_1][1], helpers)
|
|
40
|
+
]}]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
value
|
|
44
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'readable'
|
|
4
|
+
|
|
5
|
+
module Primate
|
|
6
|
+
class UploadedFile
|
|
7
|
+
attr_reader :field, :filename, :content_type, :size
|
|
8
|
+
|
|
9
|
+
def initialize(field:, name:, type:, size:, bytes:)
|
|
10
|
+
@field = field
|
|
11
|
+
@filename = name
|
|
12
|
+
@content_type = type
|
|
13
|
+
@size = size
|
|
14
|
+
@io = Primate::Readable.new(bytes, type)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def io = @io
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/primate/url.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Primate
|
|
4
|
+
class URL
|
|
5
|
+
attr_reader :href, :origin, :protocol, :username, :password, :host,
|
|
6
|
+
:hostname, :port, :pathname, :search, :hash
|
|
7
|
+
|
|
8
|
+
# initialize URL from JavaScript URL object
|
|
9
|
+
# @param url [Object] JavaScript URL object from the runtime
|
|
10
|
+
def initialize(url)
|
|
11
|
+
@href = url['href'].to_s
|
|
12
|
+
@origin = url['origin'].to_s
|
|
13
|
+
@protocol = url['protocol'].to_s
|
|
14
|
+
@username = url['username'].to_s
|
|
15
|
+
@password = url['password'].to_s
|
|
16
|
+
@host = url['host'].to_s
|
|
17
|
+
@hostname = url['hostname'].to_s
|
|
18
|
+
@port = url['port'].to_s
|
|
19
|
+
@pathname = url['pathname'].to_s
|
|
20
|
+
@search = url['search'].to_s
|
|
21
|
+
@hash = url['hash'].to_s
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/primate.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "primate/version"
|
|
4
|
+
|
|
5
|
+
module PrimateInternal
|
|
6
|
+
def self.set_session(session, helpers)
|
|
7
|
+
require_relative "primate/session"
|
|
8
|
+
session_instance = SessionInstance.new(session, helpers)
|
|
9
|
+
Session.set_current(session_instance)
|
|
10
|
+
end
|
|
11
|
+
end
|
data/sig/primate.rbs
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# RBS type signatures for Primate Ruby gem
|
|
2
|
+
|
|
3
|
+
module Primate
|
|
4
|
+
VERSION: String
|
|
5
|
+
|
|
6
|
+
class URL
|
|
7
|
+
attr_reader href: String
|
|
8
|
+
attr_reader origin: String
|
|
9
|
+
attr_reader protocol: String
|
|
10
|
+
attr_reader username: String
|
|
11
|
+
attr_reader password: String
|
|
12
|
+
attr_reader host: String
|
|
13
|
+
attr_reader hostname: String
|
|
14
|
+
attr_reader port: String
|
|
15
|
+
attr_reader pathname: String
|
|
16
|
+
attr_reader search: String
|
|
17
|
+
attr_reader hash: String
|
|
18
|
+
|
|
19
|
+
def initialize: (untyped url) -> void
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class Readable
|
|
23
|
+
attr_reader content_type: String?
|
|
24
|
+
|
|
25
|
+
def initialize: (untyped typed_array, String? content_type) -> void
|
|
26
|
+
def size: () -> Integer
|
|
27
|
+
def eof?: () -> bool
|
|
28
|
+
def rewind: () -> self
|
|
29
|
+
def read: (?Integer? n) -> String
|
|
30
|
+
def bytes: (?Integer? n) -> Array[Integer]
|
|
31
|
+
def peek: (Integer n) -> Array[Integer]
|
|
32
|
+
def head: (?Integer n) -> Array[Integer]
|
|
33
|
+
def each_chunk: (?Integer chunk) { (String) -> void } -> self
|
|
34
|
+
| (?Integer chunk) -> Enumerator[String, self]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class UploadedFile
|
|
38
|
+
attr_reader field: String
|
|
39
|
+
attr_reader filename: String
|
|
40
|
+
attr_reader content_type: String
|
|
41
|
+
attr_reader size: Integer
|
|
42
|
+
|
|
43
|
+
def initialize: (field: String, name: String, type: String, size: Integer, bytes: untyped) -> void
|
|
44
|
+
def io: () -> Readable
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class RequestBody
|
|
49
|
+
def initialize: (untyped body, untyped helpers) -> void
|
|
50
|
+
def json: () -> Hash[String, untyped]
|
|
51
|
+
def text: () -> String
|
|
52
|
+
def fields: () -> Hash[String, untyped]
|
|
53
|
+
def files: () -> Array[Primate::UploadedFile]
|
|
54
|
+
def binary: () -> Primate::Readable
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class RequestBag
|
|
58
|
+
def initialize: (untyped data, untyped helpers) -> void
|
|
59
|
+
def get: (String key) -> String
|
|
60
|
+
def []: (String key) -> String
|
|
61
|
+
def try: (String key) -> String?
|
|
62
|
+
def has?: (String key) -> bool
|
|
63
|
+
def parse: (untyped schema, ?bool coerce) -> untyped
|
|
64
|
+
def to_h: () -> Hash[String, untyped]
|
|
65
|
+
def size: () -> Integer
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class Request
|
|
69
|
+
attr_reader url: Primate::URL
|
|
70
|
+
attr_reader body: RequestBody
|
|
71
|
+
attr_reader path: RequestBag
|
|
72
|
+
attr_reader query: RequestBag
|
|
73
|
+
attr_reader headers: RequestBag
|
|
74
|
+
attr_reader cookies: RequestBag
|
|
75
|
+
|
|
76
|
+
def initialize: (untyped request, untyped helpers) -> void
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class SessionInstance
|
|
80
|
+
def initialize: (untyped session, untyped helpers) -> void
|
|
81
|
+
def id: () -> String
|
|
82
|
+
def exists: () -> bool
|
|
83
|
+
def create: (Hash[String, untyped] data) -> void
|
|
84
|
+
def get: () -> Hash[String, untyped]
|
|
85
|
+
def try: () -> Hash[String, untyped]
|
|
86
|
+
def set: (Hash[String, untyped] data) -> void
|
|
87
|
+
def destroy: () -> void
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
module Session
|
|
91
|
+
def self.set_current: (SessionInstance session_instance) -> void
|
|
92
|
+
def self.id: () -> String?
|
|
93
|
+
def self.exists?: () -> bool
|
|
94
|
+
def self.create: (Hash[String, untyped] data) -> void
|
|
95
|
+
def self.get: () -> Hash[String, untyped]
|
|
96
|
+
def self.try: () -> Hash[String, untyped]
|
|
97
|
+
def self.set: (Hash[String, untyped] data) -> void
|
|
98
|
+
def self.destroy: () -> void
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
module Response
|
|
102
|
+
def self.view: (String name, ?Hash[String, untyped] props, ?Hash[String, untyped] options) -> Hash[Symbol, untyped]
|
|
103
|
+
def self.redirect: (String location, ?Hash[String, untyped] options) -> Hash[Symbol, untyped]
|
|
104
|
+
def self.error: (?Hash[String, untyped] options) -> Hash[Symbol, untyped]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
module Route
|
|
108
|
+
def self.get: () { (Request) -> untyped } -> void
|
|
109
|
+
def self.post: () { (Request) -> untyped } -> void
|
|
110
|
+
def self.put: () { (Request) -> untyped } -> void
|
|
111
|
+
def self.patch: () { (Request) -> untyped } -> void
|
|
112
|
+
def self.delete: () { (Request) -> untyped } -> void
|
|
113
|
+
def self.head: () { (Request) -> untyped } -> void
|
|
114
|
+
def self.options: () { (Request) -> untyped } -> void
|
|
115
|
+
def self.connect: () { (Request) -> untyped } -> void
|
|
116
|
+
def self.trace: () { (Request) -> untyped } -> void
|
|
117
|
+
def self.routes: () -> Hash[String, Proc]
|
|
118
|
+
def self.set_session: (untyped session, untyped helpers) -> void
|
|
119
|
+
def self.call_route: (String method, Request request) -> untyped
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
module Pema
|
|
123
|
+
class ValidationError < StandardError
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
class Field
|
|
127
|
+
def parse: (untyped value, ?bool coerce) -> untyped
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
class StringType < Field
|
|
131
|
+
def parse: (untyped value, ?bool coerce) -> String
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
class BooleanType < Field
|
|
135
|
+
def parse: (untyped value, ?bool coerce) -> bool
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
class IntType < Field
|
|
139
|
+
def parse: (untyped value, ?bool coerce) -> Integer
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
class FloatType < Field
|
|
143
|
+
def parse: (untyped value, ?bool coerce) -> Float
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
class Schema
|
|
147
|
+
def initialize: (Hash[String, Field] fields) -> void
|
|
148
|
+
def parse: (Hash[String, untyped] data, ?bool coerce) -> Hash[String, untyped]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def self.string: () -> StringType
|
|
152
|
+
def self.boolean: () -> BooleanType
|
|
153
|
+
def self.int: () -> IntType
|
|
154
|
+
def self.float: () -> FloatType
|
|
155
|
+
def self.schema: (Hash[String, Field] fields) -> Schema
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
module PrimateInternal
|
|
159
|
+
def self.set_session: (untyped session, untyped helpers) -> void
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def type: (untyped value, untyped helpers) -> untyped
|
metadata
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: primate-run
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Primate Team
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bundler
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :development
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rake
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '13.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '13.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: ruby-lsp
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.26'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.26'
|
|
54
|
+
description: Provides Ruby support for building web applications with the Primate
|
|
55
|
+
framework
|
|
56
|
+
email:
|
|
57
|
+
- terrablue@proton.me
|
|
58
|
+
executables: []
|
|
59
|
+
extensions: []
|
|
60
|
+
extra_rdoc_files: []
|
|
61
|
+
files:
|
|
62
|
+
- lib/primate.rb
|
|
63
|
+
- lib/primate/pema.rb
|
|
64
|
+
- lib/primate/readable.rb
|
|
65
|
+
- lib/primate/request.rb
|
|
66
|
+
- lib/primate/request_bag.rb
|
|
67
|
+
- lib/primate/request_body.rb
|
|
68
|
+
- lib/primate/response.rb
|
|
69
|
+
- lib/primate/route.rb
|
|
70
|
+
- lib/primate/session.rb
|
|
71
|
+
- lib/primate/type.rb
|
|
72
|
+
- lib/primate/uploaded_file.rb
|
|
73
|
+
- lib/primate/url.rb
|
|
74
|
+
- lib/primate/version.rb
|
|
75
|
+
- sig/primate.rbs
|
|
76
|
+
homepage: https://primate.run
|
|
77
|
+
licenses:
|
|
78
|
+
- MIT
|
|
79
|
+
metadata:
|
|
80
|
+
homepage_uri: https://primate.run
|
|
81
|
+
source_code_uri: https://github.com/primate-run/ruby
|
|
82
|
+
bug_tracker_uri: https://github.com/primate-run/ruby/issues
|
|
83
|
+
rdoc_options: []
|
|
84
|
+
require_paths:
|
|
85
|
+
- lib
|
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
version: 3.0.0
|
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
requirements: []
|
|
97
|
+
rubygems_version: 3.6.9
|
|
98
|
+
specification_version: 4
|
|
99
|
+
summary: Ruby route handlers for the Primate web framework
|
|
100
|
+
test_files: []
|