Merge branch 'development_actions007' of github.com:freeflowuniverse/herolib into development_actions007
This commit is contained in:
@@ -30,7 +30,9 @@ mut:
|
||||
// Returns:
|
||||
// - A pointer to a new Client instance
|
||||
pub fn new_client(client Client) &Client {
|
||||
return &Client{...client}
|
||||
return &Client{
|
||||
...client
|
||||
}
|
||||
}
|
||||
|
||||
// SendParams defines configuration options for sending JSON-RPC requests.
|
||||
@@ -40,9 +42,9 @@ pub struct SendParams {
|
||||
pub:
|
||||
// Maximum time in seconds to wait for a response (default: 60)
|
||||
timeout int = 60
|
||||
|
||||
|
||||
// Number of times to retry the request if it fails
|
||||
retry int
|
||||
retry int
|
||||
}
|
||||
|
||||
// send sends a JSON-RPC request with parameters of type T and expects a response with result of type D.
|
||||
@@ -61,16 +63,14 @@ pub:
|
||||
pub fn (mut c Client) send[T, D](request RequestGeneric[T], params SendParams) !D {
|
||||
// Send the encoded request through the transport layer
|
||||
response_json := c.transport.send(request.encode(), params)!
|
||||
|
||||
|
||||
// Decode the response JSON into a strongly-typed response object
|
||||
response := decode_response_generic[D](response_json) or {
|
||||
return error('Unable to decode response.\n- Response: ${response_json}\n- Error: ${err}')
|
||||
}
|
||||
|
||||
// Validate the response according to the JSON-RPC specification
|
||||
response.validate() or {
|
||||
return error('Received invalid response: ${err}')
|
||||
}
|
||||
response.validate() or { return error('Received invalid response: ${err}') }
|
||||
|
||||
// Ensure the response ID matches the request ID to prevent response/request mismatch
|
||||
if response.id != request.id {
|
||||
@@ -79,4 +79,4 @@ pub fn (mut c Client) send[T, D](request RequestGeneric[T], params SendParams) !
|
||||
|
||||
// Return the result or propagate any error from the response
|
||||
return response.result()!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,14 +31,14 @@ fn (t TestRPCTransportClient) send(request_json string, params SendParams) !stri
|
||||
new_response(request.id, request.params)
|
||||
} else if request.method == 'test_error' {
|
||||
error := RPCError{
|
||||
code: 1
|
||||
code: 1
|
||||
message: 'intentional jsonrpc error response'
|
||||
}
|
||||
new_error_response(request.id, error)
|
||||
} else {
|
||||
new_error_response(request.id, method_not_found)
|
||||
}
|
||||
|
||||
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ fn test_send_json_rpc() {
|
||||
assert err.code() == 1
|
||||
assert err.msg() == 'intentional jsonrpc error response'
|
||||
}
|
||||
|
||||
|
||||
// Test case 3: Method not found error
|
||||
request2 := new_request_generic[string]('nonexistent_method', '')
|
||||
if response2 := client.send[string, string](request2) {
|
||||
|
||||
@@ -30,7 +30,9 @@ pub type ProcedureHandler = fn (payload string) !string
|
||||
// Returns:
|
||||
// - A pointer to a new Handler instance or an error if creation fails
|
||||
pub fn new_handler(handler Handler) !&Handler {
|
||||
return &Handler{...handler}
|
||||
return &Handler{
|
||||
...handler
|
||||
}
|
||||
}
|
||||
|
||||
// register_procedure registers a new procedure handler for the specified method.
|
||||
@@ -69,7 +71,6 @@ pub fn (handler Handler) handle(message string) !string {
|
||||
log.error('debugzo1')
|
||||
method := decode_request_method(message)!
|
||||
// log.info('Handling remote procedure call to method: ${method}')
|
||||
log.error('debugzo2')
|
||||
// Look up the procedure handler for the requested method
|
||||
procedure_func := handler.procedures[method] or {
|
||||
// log.error('No procedure handler for method ${method} found')
|
||||
|
||||
@@ -56,17 +56,17 @@ fn method_error(text string) !string {
|
||||
fn method_echo_handler(data string) !string {
|
||||
// Decode the request with string parameters
|
||||
request := decode_request_generic[string](data)!
|
||||
|
||||
|
||||
// Call the echo method and handle any errors
|
||||
result := method_echo(request.params) or {
|
||||
// If an error occurs, create an error response
|
||||
response := new_error_response(request.id,
|
||||
code: err.code()
|
||||
code: err.code()
|
||||
message: err.msg()
|
||||
)
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
|
||||
// Create a success response with the result
|
||||
response := new_response_generic(request.id, result)
|
||||
return response.encode()
|
||||
@@ -83,17 +83,17 @@ fn method_echo_handler(data string) !string {
|
||||
fn method_echo_struct_handler(data string) !string {
|
||||
// Decode the request with TestStruct parameters
|
||||
request := decode_request_generic[TestStruct](data)!
|
||||
|
||||
|
||||
// Call the echo struct method and handle any errors
|
||||
result := method_echo_struct(request.params) or {
|
||||
// If an error occurs, create an error response
|
||||
response := new_error_response(request.id,
|
||||
code: err.code()
|
||||
code: err.code()
|
||||
message: err.msg()
|
||||
)
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
|
||||
// Create a success response with the struct result
|
||||
response := new_response_generic[TestStruct](request.id, result)
|
||||
return response.encode()
|
||||
@@ -110,17 +110,17 @@ fn method_echo_struct_handler(data string) !string {
|
||||
fn method_error_handler(data string) !string {
|
||||
// Decode the request with string parameters
|
||||
request := decode_request_generic[string](data)!
|
||||
|
||||
|
||||
// Call the error method, which always returns an error
|
||||
result := method_error(request.params) or {
|
||||
// Create an error response with the error details
|
||||
response := new_error_response(request.id,
|
||||
code: err.code()
|
||||
code: err.code()
|
||||
message: err.msg()
|
||||
)
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
|
||||
// This code should never be reached since method_error always returns an error
|
||||
response := new_response_generic(request.id, result)
|
||||
return response.encode()
|
||||
@@ -141,9 +141,9 @@ fn test_handle() {
|
||||
// Create a new handler with three test procedures
|
||||
handler := new_handler(Handler{
|
||||
procedures: {
|
||||
'method_echo': method_echo_handler
|
||||
'method_echo': method_echo_handler
|
||||
'method_echo_struct': method_echo_struct_handler
|
||||
'method_error': method_error_handler
|
||||
'method_error': method_error_handler
|
||||
}
|
||||
})!
|
||||
|
||||
|
||||
@@ -7,45 +7,45 @@ module jsonrpc
|
||||
// This error is returned when the server is unable to parse the request.
|
||||
// Error code: -32700
|
||||
pub const parse_error = RPCError{
|
||||
code: -32700
|
||||
code: -32700
|
||||
message: 'Parse error'
|
||||
data: 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.'
|
||||
data: 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.'
|
||||
}
|
||||
|
||||
// invalid_request indicates that the sent JSON is not a valid Request object.
|
||||
// This error is returned when the request object doesn't conform to the JSON-RPC 2.0 specification.
|
||||
// Error code: -32600
|
||||
pub const invalid_request = RPCError{
|
||||
code: -32600
|
||||
code: -32600
|
||||
message: 'Invalid Request'
|
||||
data: 'The JSON sent is not a valid Request object.'
|
||||
data: 'The JSON sent is not a valid Request object.'
|
||||
}
|
||||
|
||||
// method_not_found indicates that the requested method doesn't exist or is not available.
|
||||
// This error is returned when the method specified in the request is not supported.
|
||||
// Error code: -32601
|
||||
pub const method_not_found = RPCError{
|
||||
code: -32601
|
||||
code: -32601
|
||||
message: 'Method not found'
|
||||
data: 'The method does not exist / is not available.'
|
||||
data: 'The method does not exist / is not available.'
|
||||
}
|
||||
|
||||
// invalid_params indicates that the method parameters are invalid.
|
||||
// This error is returned when the parameters provided to the method are incorrect or incompatible.
|
||||
// Error code: -32602
|
||||
pub const invalid_params = RPCError{
|
||||
code: -32602
|
||||
code: -32602
|
||||
message: 'Invalid params'
|
||||
data: 'Invalid method parameter(s).'
|
||||
data: 'Invalid method parameter(s).'
|
||||
}
|
||||
|
||||
// internal_error indicates an internal JSON-RPC error.
|
||||
// This is a generic server-side error when no more specific error is applicable.
|
||||
// Error code: -32603
|
||||
pub const internal_error = RPCError{
|
||||
code: -32603
|
||||
code: -32603
|
||||
message: 'Internal Error'
|
||||
data: 'Internal JSON-RPC error.'
|
||||
data: 'Internal JSON-RPC error.'
|
||||
}
|
||||
|
||||
// RPCError represents a JSON-RPC 2.0 error object as defined in the specification.
|
||||
@@ -55,13 +55,13 @@ pub struct RPCError {
|
||||
pub mut:
|
||||
// Numeric error code. Predefined codes are in the range -32768 to -32000.
|
||||
// Custom error codes should be outside this range.
|
||||
code int
|
||||
|
||||
code int
|
||||
|
||||
// Short description of the error
|
||||
message string
|
||||
|
||||
|
||||
// Additional information about the error (optional)
|
||||
data string
|
||||
data string
|
||||
}
|
||||
|
||||
// new_error creates a new error response for a given request ID.
|
||||
@@ -76,8 +76,8 @@ pub mut:
|
||||
pub fn new_error(id int, error RPCError) Response {
|
||||
return Response{
|
||||
jsonrpc: jsonrpc_version
|
||||
error_: error
|
||||
id: id
|
||||
error_: error
|
||||
id: id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,4 +106,4 @@ pub fn (err RPCError) code() int {
|
||||
// - true if the error is empty, false otherwise
|
||||
pub fn (err RPCError) is_empty() bool {
|
||||
return err.code == 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,25 +4,25 @@ module jsonrpc
|
||||
// It contains all the required fields according to the JSON-RPC 2.0 specification.
|
||||
// See: https://www.jsonrpc.org/specification#notification
|
||||
pub struct Notification {
|
||||
pub mut:
|
||||
pub mut:
|
||||
// The JSON-RPC protocol version, must be exactly "2.0"
|
||||
jsonrpc string = "2.0" @[required]
|
||||
|
||||
jsonrpc string = '2.0' @[required]
|
||||
|
||||
// The name of the method to be invoked on the server
|
||||
method string @[required]
|
||||
method string @[required]
|
||||
}
|
||||
|
||||
// Notification represents a JSON-RPC 2.0 notification object.
|
||||
// It contains all the required fields according to the JSON-RPC 2.0 specification.
|
||||
// See: https://www.jsonrpc.org/specification#notification
|
||||
pub struct NotificationGeneric[T] {
|
||||
pub mut:
|
||||
pub mut:
|
||||
// The JSON-RPC protocol version, must be exactly "2.0"
|
||||
jsonrpc string = "2.0" @[required]
|
||||
|
||||
jsonrpc string = '2.0' @[required]
|
||||
|
||||
// The name of the method to be invoked on the server
|
||||
method string @[required]
|
||||
params ?T
|
||||
method string @[required]
|
||||
params ?T
|
||||
}
|
||||
|
||||
// new_notification creates a new JSON-RPC notification with the specified method and parameters.
|
||||
@@ -36,15 +36,15 @@ pub struct NotificationGeneric[T] {
|
||||
// - A fully initialized Notification object
|
||||
pub fn new_notification[T](method string, params T) NotificationGeneric[T] {
|
||||
return NotificationGeneric[T]{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
method: method
|
||||
params: params
|
||||
jsonrpc: jsonrpc_version
|
||||
method: method
|
||||
params: params
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_blank_notification(method string) Notification {
|
||||
return Notification{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
method: method
|
||||
jsonrpc: jsonrpc_version
|
||||
method: method
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,18 +10,18 @@ import rand
|
||||
pub struct Request {
|
||||
pub mut:
|
||||
// The JSON-RPC protocol version, must be exactly "2.0"
|
||||
jsonrpc string @[required]
|
||||
|
||||
jsonrpc string @[required]
|
||||
|
||||
// The name of the method to be invoked on the server
|
||||
method string @[required]
|
||||
|
||||
method string @[required]
|
||||
|
||||
// The parameters to the method, encoded as a JSON string
|
||||
// This can be omitted if the method doesn't require parameters
|
||||
params string
|
||||
|
||||
params string
|
||||
|
||||
// An identifier established by the client that must be included in the response
|
||||
// This is used to correlate requests with their corresponding responses
|
||||
id int @[required]
|
||||
id int @[required]
|
||||
}
|
||||
|
||||
// new_request creates a new JSON-RPC request with the specified method and parameters.
|
||||
@@ -35,10 +35,10 @@ pub mut:
|
||||
// - A fully initialized Request object
|
||||
pub fn new_request(method string, params string) Request {
|
||||
return Request{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
method: method
|
||||
params: params
|
||||
id: rand.i32() // Automatically generate a unique ID using UUID v4
|
||||
jsonrpc: jsonrpc_version
|
||||
method: method
|
||||
params: params
|
||||
id: rand.i32() // Automatically generate a unique ID using UUID v4
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,15 +83,15 @@ pub struct RequestGeneric[T] {
|
||||
pub mut:
|
||||
// The JSON-RPC protocol version, must be exactly "2.0"
|
||||
jsonrpc string @[required]
|
||||
|
||||
|
||||
// The name of the method to be invoked on the server
|
||||
method string @[required]
|
||||
|
||||
method string @[required]
|
||||
|
||||
// The parameters to the method, with a specific type T
|
||||
params T
|
||||
|
||||
params T
|
||||
|
||||
// An identifier established by the client
|
||||
id int @[required]
|
||||
id int @[required]
|
||||
}
|
||||
|
||||
// new_request_generic creates a new generic JSON-RPC request with strongly-typed parameters.
|
||||
@@ -105,10 +105,10 @@ pub mut:
|
||||
// - A fully initialized RequestGeneric object with parameters of type T
|
||||
pub fn new_request_generic[T](method string, params T) RequestGeneric[T] {
|
||||
return RequestGeneric[T]{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
method: method
|
||||
params: params
|
||||
id: rand.i32()
|
||||
jsonrpc: jsonrpc_version
|
||||
method: method
|
||||
params: params
|
||||
id: rand.i32()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,4 +159,4 @@ pub fn decode_request_generic[T](data string) !RequestGeneric[T] {
|
||||
// - A JSON string representation of the RequestGeneric object
|
||||
pub fn (req RequestGeneric[T]) encode[T]() string {
|
||||
return json2.encode(req)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ module jsonrpc
|
||||
|
||||
fn test_new_request() {
|
||||
request := new_request('test_method', 'test_params')
|
||||
assert request.jsonrpc == jsonrpc.jsonrpc_version
|
||||
assert request.jsonrpc == jsonrpc_version
|
||||
assert request.method == 'test_method'
|
||||
assert request.params == 'test_params'
|
||||
assert request.id != -1 // Ensure the ID is generated
|
||||
@@ -11,7 +11,7 @@ fn test_new_request() {
|
||||
fn test_decode_request() {
|
||||
data := '{"jsonrpc":"2.0","method":"test_method","params":"test_params","id":"123"}'
|
||||
request := decode_request(data) or {
|
||||
assert false, 'Failed to decode request: $err'
|
||||
assert false, 'Failed to decode request: ${err}'
|
||||
return
|
||||
}
|
||||
assert request.jsonrpc == '2.0'
|
||||
@@ -23,13 +23,16 @@ fn test_decode_request() {
|
||||
fn test_request_encode() {
|
||||
request := new_request('test_method', 'test_params')
|
||||
json := request.encode()
|
||||
assert json.contains('"jsonrpc"') && json.contains('"method"') && json.contains('"params"') && json.contains('"id"')
|
||||
assert json.contains('"jsonrpc"') && json.contains('"method"') && json.contains('"params"')
|
||||
&& json.contains('"id"')
|
||||
}
|
||||
|
||||
fn test_new_request_generic() {
|
||||
params := {'key': 'value'}
|
||||
params := {
|
||||
'key': 'value'
|
||||
}
|
||||
request := new_request_generic('test_method', params)
|
||||
assert request.jsonrpc == jsonrpc.jsonrpc_version
|
||||
assert request.jsonrpc == jsonrpc_version
|
||||
assert request.method == 'test_method'
|
||||
assert request.params == params
|
||||
assert request.id != -1 // Ensure the ID is generated
|
||||
@@ -38,7 +41,7 @@ fn test_new_request_generic() {
|
||||
fn test_decode_request_id() {
|
||||
data := '{"jsonrpc":"2.0","method":"test_method","params":"test_params","id":"123"}'
|
||||
id := decode_request_id(data) or {
|
||||
assert false, 'Failed to decode request ID: $err'
|
||||
assert false, 'Failed to decode request ID: ${err}'
|
||||
return
|
||||
}
|
||||
assert id == 123
|
||||
@@ -47,7 +50,7 @@ fn test_decode_request_id() {
|
||||
fn test_decode_request_method() {
|
||||
data := '{"jsonrpc":"2.0","method":"test_method","params":"test_params","id":"123"}'
|
||||
method := decode_request_method(data) or {
|
||||
assert false, 'Failed to decode request method: $err'
|
||||
assert false, 'Failed to decode request method: ${err}'
|
||||
return
|
||||
}
|
||||
assert method == 'test_method'
|
||||
@@ -56,18 +59,23 @@ fn test_decode_request_method() {
|
||||
fn test_decode_request_generic() {
|
||||
data := '{"jsonrpc":"2.0","method":"test_method","params":{"key":"value"},"id":123}'
|
||||
request := decode_request_generic[map[string]string](data) or {
|
||||
assert false, 'Failed to decode generic request: $err'
|
||||
assert false, 'Failed to decode generic request: ${err}'
|
||||
return
|
||||
}
|
||||
assert request.jsonrpc == '2.0'
|
||||
assert request.method == 'test_method'
|
||||
assert request.params == {'key': 'value'}
|
||||
assert request.params == {
|
||||
'key': 'value'
|
||||
}
|
||||
assert request.id == 123
|
||||
}
|
||||
|
||||
fn test_request_generic_encode() {
|
||||
params := {'key': 'value'}
|
||||
params := {
|
||||
'key': 'value'
|
||||
}
|
||||
request := new_request_generic('test_method', params)
|
||||
json := request.encode()
|
||||
assert json.contains('"jsonrpc"') && json.contains('"method"') && json.contains('"params"') && json.contains('"id"')
|
||||
}
|
||||
assert json.contains('"jsonrpc"') && json.contains('"method"') && json.contains('"params"')
|
||||
&& json.contains('"id"')
|
||||
}
|
||||
|
||||
@@ -13,15 +13,15 @@ pub struct Response {
|
||||
pub:
|
||||
// The JSON-RPC protocol version, must be exactly "2.0"
|
||||
jsonrpc string @[required]
|
||||
|
||||
|
||||
// The result of the method invocation (only present if the call was successful)
|
||||
result ?string
|
||||
|
||||
result ?string
|
||||
|
||||
// Error object if the request failed (only present if the call failed)
|
||||
error_ ?RPCError @[json: 'error']
|
||||
|
||||
error_ ?RPCError @[json: 'error']
|
||||
|
||||
// Must match the id of the request that generated this response
|
||||
id int @[required]
|
||||
id int @[required]
|
||||
}
|
||||
|
||||
// new_response creates a successful JSON-RPC response with the given result.
|
||||
@@ -34,9 +34,9 @@ pub:
|
||||
// - A Response object containing the result
|
||||
pub fn new_response(id int, result string) Response {
|
||||
return Response{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
result: result
|
||||
id: id
|
||||
jsonrpc: jsonrpc_version
|
||||
result: result
|
||||
id: id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,9 +50,9 @@ pub fn new_response(id int, result string) Response {
|
||||
// - A Response object containing the error
|
||||
pub fn new_error_response(id int, error RPCError) Response {
|
||||
return Response{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
error_: error
|
||||
id: id
|
||||
jsonrpc: jsonrpc_version
|
||||
error_: error
|
||||
id: id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,19 +77,19 @@ pub fn decode_response(data string) !Response {
|
||||
|
||||
// Handle error responses
|
||||
if err := raw_map['error'] {
|
||||
id_any := raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}
|
||||
return Response {
|
||||
id: id_any.int()
|
||||
id_any := raw_map['id'] or { return error('Invalid JSONRPC response, no ID Field found') }
|
||||
return Response{
|
||||
id: id_any.int()
|
||||
jsonrpc: jsonrpc_version
|
||||
error_: json2.decode[RPCError](err.str())!
|
||||
error_: json2.decode[RPCError](err.str())!
|
||||
}
|
||||
}
|
||||
|
||||
// Handle successful responses
|
||||
return Response {
|
||||
id: raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}.int()
|
||||
return Response{
|
||||
id: raw_map['id'] or { return error('Invalid JSONRPC response, no ID Field found') }.int()
|
||||
jsonrpc: jsonrpc_version
|
||||
result: raw_map['result']!.str()
|
||||
result: raw_map['result']!.str()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,8 +148,9 @@ pub fn (resp Response) error() ?RPCError {
|
||||
pub fn (resp Response) result() !string {
|
||||
if err := resp.error() {
|
||||
return err
|
||||
} // Ensure no error is present
|
||||
return resp.result or {''}
|
||||
}
|
||||
// Ensure no error is present
|
||||
return resp.result or { '' }
|
||||
}
|
||||
|
||||
// ResponseGeneric is a type-safe version of the Response struct that allows
|
||||
@@ -159,15 +160,15 @@ pub struct ResponseGeneric[D] {
|
||||
pub mut:
|
||||
// The JSON-RPC protocol version, must be exactly "2.0"
|
||||
jsonrpc string @[required]
|
||||
|
||||
|
||||
// The result of the method invocation with a specific type D
|
||||
result ?D
|
||||
|
||||
result ?D
|
||||
|
||||
// Error object if the request failed
|
||||
error_ ?RPCError @[json: 'error']
|
||||
|
||||
error_ ?RPCError @[json: 'error']
|
||||
|
||||
// Must match the id of the request that generated this response
|
||||
id int @[required]
|
||||
id int @[required]
|
||||
}
|
||||
|
||||
// new_response_generic creates a successful generic JSON-RPC response with a strongly-typed result.
|
||||
@@ -180,9 +181,9 @@ pub mut:
|
||||
// - A ResponseGeneric object with result of type D
|
||||
pub fn new_response_generic[D](id int, result D) ResponseGeneric[D] {
|
||||
return ResponseGeneric[D]{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
result: result
|
||||
id: id
|
||||
jsonrpc: jsonrpc_version
|
||||
result: result
|
||||
id: id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +197,7 @@ pub fn new_response_generic[D](id int, result D) ResponseGeneric[D] {
|
||||
// - A ResponseGeneric object with result of type D, or an error if parsing fails
|
||||
pub fn decode_response_generic[D](data string) !ResponseGeneric[D] {
|
||||
// Debug output - consider removing in production
|
||||
|
||||
|
||||
raw := json2.raw_decode(data)!
|
||||
raw_map := raw.as_map()
|
||||
|
||||
@@ -209,19 +210,21 @@ pub fn decode_response_generic[D](data string) !ResponseGeneric[D] {
|
||||
|
||||
// Handle error responses
|
||||
if err := raw_map['error'] {
|
||||
return ResponseGeneric[D] {
|
||||
id: raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}.int()
|
||||
return ResponseGeneric[D]{
|
||||
id: raw_map['id'] or {
|
||||
return error('Invalid JSONRPC response, no ID Field found')
|
||||
}.int()
|
||||
jsonrpc: jsonrpc_version
|
||||
error_: json2.decode[RPCError](err.str())!
|
||||
error_: json2.decode[RPCError](err.str())!
|
||||
}
|
||||
}
|
||||
|
||||
// Handle successful responses
|
||||
resp := json.decode(ResponseGeneric[D], data)!
|
||||
return ResponseGeneric[D] {
|
||||
id: raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}.int()
|
||||
return ResponseGeneric[D]{
|
||||
id: raw_map['id'] or { return error('Invalid JSONRPC response, no ID Field found') }.int()
|
||||
jsonrpc: jsonrpc_version
|
||||
result: resp.result
|
||||
result: resp.result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +279,7 @@ pub fn (resp ResponseGeneric[D]) error() ?RPCError {
|
||||
pub fn (resp ResponseGeneric[D]) result() !D {
|
||||
if err := resp.error() {
|
||||
return err
|
||||
} // Ensure no error is present
|
||||
return resp.result or {D{}}
|
||||
}
|
||||
}
|
||||
// Ensure no error is present
|
||||
return resp.result or { D{} }
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ module jsonrpc
|
||||
|
||||
fn test_new_response() {
|
||||
response := new_response(123, 'test_result')
|
||||
assert response.jsonrpc == jsonrpc.jsonrpc_version
|
||||
assert response.jsonrpc == jsonrpc_version
|
||||
assert response.id == 123
|
||||
|
||||
|
||||
assert response.is_result()
|
||||
assert !response.is_error() // Ensure no error is set
|
||||
result := response.result() or {
|
||||
@@ -16,18 +16,18 @@ fn test_new_response() {
|
||||
|
||||
fn test_new_error_response() {
|
||||
error := RPCError{
|
||||
code: 123
|
||||
code: 123
|
||||
message: 'Test error'
|
||||
data: 'Error details'
|
||||
data: 'Error details'
|
||||
}
|
||||
response := new_error_response(123, error)
|
||||
assert response.jsonrpc == jsonrpc.jsonrpc_version
|
||||
assert response.jsonrpc == jsonrpc_version
|
||||
|
||||
response.validate()!
|
||||
assert response.is_error()
|
||||
assert !response.is_result() // Ensure no result is set
|
||||
assert response.id == 123
|
||||
|
||||
|
||||
response_error := response.error()?
|
||||
assert response_error == error
|
||||
}
|
||||
@@ -35,7 +35,7 @@ fn test_new_error_response() {
|
||||
fn test_decode_response() {
|
||||
data := '{"jsonrpc":"2.0","result":"test_result","id":"123"}'
|
||||
response := decode_response(data) or {
|
||||
assert false, 'Failed to decode response: $err'
|
||||
assert false, 'Failed to decode response: ${err}'
|
||||
return
|
||||
}
|
||||
assert response.jsonrpc == '2.0'
|
||||
@@ -58,18 +58,18 @@ fn test_response_encode() {
|
||||
|
||||
fn test_response_validate() {
|
||||
response := new_response(123, 'test_result')
|
||||
response.validate() or { assert false, 'Validation failed for valid response: $err' }
|
||||
response.validate() or { assert false, 'Validation failed for valid response: ${err}' }
|
||||
|
||||
error := RPCError{
|
||||
code: 123
|
||||
code: 123
|
||||
message: 'Test error'
|
||||
data: 'Error details'
|
||||
data: 'Error details'
|
||||
}
|
||||
invalid_response := Response{
|
||||
jsonrpc: '2.0'
|
||||
result: 'test_result'
|
||||
error_: error
|
||||
id: 123
|
||||
result: 'test_result'
|
||||
error_: error
|
||||
id: 123
|
||||
}
|
||||
invalid_response.validate() or {
|
||||
assert err.msg().contains('Response contains both error and result.')
|
||||
@@ -78,13 +78,13 @@ fn test_response_validate() {
|
||||
|
||||
fn test_response_error() {
|
||||
error := RPCError{
|
||||
code: 123
|
||||
code: 123
|
||||
message: 'Test error'
|
||||
data: 'Error details'
|
||||
data: 'Error details'
|
||||
}
|
||||
response := new_error_response(123, error)
|
||||
err := response.error() or {
|
||||
assert false, 'Failed to get error: $err'
|
||||
assert false, 'Failed to get error: ${err}'
|
||||
return
|
||||
}
|
||||
assert err.code == 123
|
||||
@@ -95,64 +95,76 @@ fn test_response_error() {
|
||||
fn test_response_result() {
|
||||
response := new_response(123, 'test_result')
|
||||
result := response.result() or {
|
||||
assert false, 'Failed to get result: $err'
|
||||
assert false, 'Failed to get result: ${err}'
|
||||
return
|
||||
}
|
||||
assert result == 'test_result'
|
||||
}
|
||||
|
||||
fn test_new_response_generic() {
|
||||
response := new_response_generic(123, {'key': 'value'})
|
||||
assert response.jsonrpc == jsonrpc.jsonrpc_version
|
||||
response := new_response_generic(123, {
|
||||
'key': 'value'
|
||||
})
|
||||
assert response.jsonrpc == jsonrpc_version
|
||||
assert response.id == 123
|
||||
|
||||
|
||||
assert response.is_result()
|
||||
assert !response.is_error() // Ensure no error is set
|
||||
result := response.result() or {
|
||||
assert false, 'Response should have result'
|
||||
return
|
||||
}
|
||||
assert result == {'key': 'value'}
|
||||
assert result == {
|
||||
'key': 'value'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_decode_response_generic() {
|
||||
data := '{"jsonrpc":"2.0","result":{"key":"value"},"id":"123"}'
|
||||
response := decode_response_generic[map[string]string](data) or {
|
||||
assert false, 'Failed to decode generic response: $err'
|
||||
assert false, 'Failed to decode generic response: ${err}'
|
||||
return
|
||||
}
|
||||
assert response.jsonrpc == '2.0'
|
||||
assert response.id == 123
|
||||
|
||||
|
||||
assert response.is_result()
|
||||
assert !response.is_error() // Ensure no error is set
|
||||
result := response.result() or {
|
||||
assert false, 'Response should have result'
|
||||
return
|
||||
}
|
||||
assert result == {'key': 'value'}
|
||||
assert result == {
|
||||
'key': 'value'
|
||||
}
|
||||
}
|
||||
|
||||
fn test_response_generic_encode() {
|
||||
response := new_response_generic(123, {'key': 'value'})
|
||||
response := new_response_generic(123, {
|
||||
'key': 'value'
|
||||
})
|
||||
json := response.encode()
|
||||
assert json.contains('"jsonrpc"') && json.contains('"result"') && json.contains('"id"')
|
||||
}
|
||||
|
||||
fn test_response_generic_validate() {
|
||||
response := new_response_generic(123, {'key': 'value'})
|
||||
response.validate() or { assert false, 'Validation failed for valid response: $err' }
|
||||
response := new_response_generic(123, {
|
||||
'key': 'value'
|
||||
})
|
||||
response.validate() or { assert false, 'Validation failed for valid response: ${err}' }
|
||||
|
||||
error := RPCError{
|
||||
code: 123
|
||||
code: 123
|
||||
message: 'Test error'
|
||||
data: 'Error details'
|
||||
data: 'Error details'
|
||||
}
|
||||
invalid_response := ResponseGeneric{
|
||||
jsonrpc: '2.0'
|
||||
result: {'key': 'value'}
|
||||
error_: error
|
||||
id: 123
|
||||
result: {
|
||||
'key': 'value'
|
||||
}
|
||||
error_: error
|
||||
id: 123
|
||||
}
|
||||
invalid_response.validate() or {
|
||||
assert err.msg().contains('Response contains both error and result.')
|
||||
@@ -161,17 +173,17 @@ fn test_response_generic_validate() {
|
||||
|
||||
fn test_response_generic_error() {
|
||||
error := RPCError{
|
||||
code: 123
|
||||
code: 123
|
||||
message: 'Test error'
|
||||
data: 'Error details'
|
||||
data: 'Error details'
|
||||
}
|
||||
response := ResponseGeneric[map[string]string]{
|
||||
jsonrpc: '2.0'
|
||||
error_: error
|
||||
id: 123
|
||||
error_: error
|
||||
id: 123
|
||||
}
|
||||
err := response.error() or {
|
||||
assert false, 'Failed to get error: $err'
|
||||
assert false, 'Failed to get error: ${err}'
|
||||
return
|
||||
}
|
||||
assert err.code == 123
|
||||
@@ -180,10 +192,14 @@ fn test_response_generic_error() {
|
||||
}
|
||||
|
||||
fn test_response_generic_result() {
|
||||
response := new_response_generic(123, {'key': 'value'})
|
||||
response := new_response_generic(123, {
|
||||
'key': 'value'
|
||||
})
|
||||
result := response.result() or {
|
||||
assert false, 'Failed to get result: $err'
|
||||
assert false, 'Failed to get result: ${err}'
|
||||
return
|
||||
}
|
||||
assert result == {'key': 'value'}
|
||||
}
|
||||
assert result == {
|
||||
'key': 'value'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ fn testfunction0_handler(data string) !string {
|
||||
result := testfunction0(request.params)
|
||||
response := jsonrpc.JsonRpcResponse[string]{
|
||||
jsonrpc: '2.0.0'
|
||||
id: request.id
|
||||
result: result
|
||||
id: request.id
|
||||
result: result
|
||||
}
|
||||
return response.to_json()
|
||||
}
|
||||
@@ -26,8 +26,8 @@ fn testfunction1_handler(data string) !string {
|
||||
result := testfunction1(request.params)
|
||||
response := jsonrpc.JsonRpcResponse[[]string]{
|
||||
jsonrpc: '2.0.0'
|
||||
id: request.id
|
||||
result: result
|
||||
id: request.id
|
||||
result: result
|
||||
}
|
||||
return response.to_json()
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
module codegen
|
||||
|
||||
import freeflowuniverse.herolib.core.code { Alias, Attribute, CodeItem, Struct, StructField, Type, type_from_symbol, Object, Array}
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef, Reference }
|
||||
import freeflowuniverse.herolib.core.code { Alias, Array, Attribute, CodeItem, Object, Struct, StructField, Type, type_from_symbol }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema, SchemaRef }
|
||||
|
||||
const vtypes = {
|
||||
'integer': 'int'
|
||||
'number': 'int'
|
||||
'number': 'int'
|
||||
'string': 'string'
|
||||
'u32': 'u32'
|
||||
'u32': 'u32'
|
||||
'boolean': 'bool'
|
||||
}
|
||||
|
||||
@@ -70,23 +70,27 @@ pub fn schema_to_type(schema Schema) Type {
|
||||
if schema.properties.len == 0 {
|
||||
if additional_props := schema.additional_properties {
|
||||
code.Map{code.String{}}
|
||||
} else {Object{schema.title}}
|
||||
} else {
|
||||
Object{schema.title}
|
||||
}
|
||||
} else {
|
||||
Object{schema.title}
|
||||
}
|
||||
else {Object{schema.title}}
|
||||
}
|
||||
}
|
||||
'array' {
|
||||
// todo: handle multiple item schemas
|
||||
// todo: handle multiple item schemas
|
||||
if items := schema.items {
|
||||
if items is []SchemaRef {
|
||||
panic('items of type []SchemaRef not implemented')
|
||||
}
|
||||
Array {
|
||||
Array{
|
||||
typ: schemaref_to_type(items as SchemaRef)
|
||||
}
|
||||
} else {
|
||||
panic('items should not be none for arrays')
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if schema.typ == 'integer' && schema.format != '' {
|
||||
match schema.format {
|
||||
'int8' { code.type_i8 }
|
||||
@@ -99,8 +103,7 @@ pub fn schema_to_type(schema Schema) Type {
|
||||
'uint64' { code.type_u64 }
|
||||
else { code.Integer{} } // Default to 'int' if the format doesn't match any known type
|
||||
}
|
||||
}
|
||||
else if schema.typ in vtypes.keys() {
|
||||
} else if schema.typ in vtypes.keys() {
|
||||
type_from_symbol(vtypes[schema.typ])
|
||||
} else if schema.title != '' {
|
||||
type_from_symbol(schema.title)
|
||||
@@ -118,26 +121,26 @@ pub fn schema_to_code(schema Schema) CodeItem {
|
||||
if schema.typ in vtypes {
|
||||
return Alias{
|
||||
name: schema.title
|
||||
typ: type_from_symbol(vtypes[schema.typ])
|
||||
typ: type_from_symbol(vtypes[schema.typ])
|
||||
}
|
||||
}
|
||||
if schema.typ == 'array' {
|
||||
if items := schema.items {
|
||||
if items is SchemaRef {
|
||||
if items is Schema {
|
||||
items_schema := items as Schema
|
||||
return Alias{
|
||||
name: schema.title
|
||||
typ: type_from_symbol('[]${items_schema.typ}')
|
||||
}
|
||||
} else if items is Reference {
|
||||
items_ref := items as Reference
|
||||
return Alias{
|
||||
name: schema.title
|
||||
typ: type_from_symbol('[]${ref_to_symbol(items_ref)}')
|
||||
if items is SchemaRef {
|
||||
if items is Schema {
|
||||
items_schema := items as Schema
|
||||
return Alias{
|
||||
name: schema.title
|
||||
typ: type_from_symbol('[]${items_schema.typ}')
|
||||
}
|
||||
} else if items is Reference {
|
||||
items_ref := items as Reference
|
||||
return Alias{
|
||||
name: schema.title
|
||||
typ: type_from_symbol('[]${ref_to_symbol(items_ref)}')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic('items of type []SchemaRef not implemented')
|
||||
}
|
||||
@@ -159,10 +162,10 @@ pub fn schema_to_struct(schema Schema) Struct {
|
||||
}
|
||||
|
||||
return Struct{
|
||||
name: schema.title
|
||||
name: schema.title
|
||||
description: schema.description
|
||||
fields: fields
|
||||
is_pub: true
|
||||
fields: fields
|
||||
is_pub: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,11 +173,11 @@ pub fn ref_to_field(schema_ref SchemaRef, name string) StructField {
|
||||
if schema_ref is Reference {
|
||||
return StructField{
|
||||
name: name
|
||||
typ: type_from_symbol(ref_to_symbol(schema_ref))
|
||||
typ: type_from_symbol(ref_to_symbol(schema_ref))
|
||||
}
|
||||
} else if schema_ref is Schema {
|
||||
mut field := StructField{
|
||||
name: name
|
||||
name: name
|
||||
description: schema_ref.description
|
||||
}
|
||||
if schema_ref.typ == 'object' || schema_ref.typ == 'array' {
|
||||
@@ -203,4 +206,4 @@ pub fn ref_to_symbol(reference Reference) string {
|
||||
|
||||
pub fn ref_to_type_from_reference(reference Reference) Type {
|
||||
return type_from_symbol(ref_to_symbol(reference))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module codegen
|
||||
|
||||
import log
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Schema, SchemaRef, Reference }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema }
|
||||
|
||||
fn test_schema_to_structs_simple() ! {
|
||||
struct_str := '
|
||||
@@ -12,17 +12,17 @@ struct TestPerson {
|
||||
}'
|
||||
|
||||
schema := Schema{
|
||||
schema: 'test'
|
||||
title: 'TestPerson'
|
||||
schema: 'test'
|
||||
title: 'TestPerson'
|
||||
description: 'person struct used for test schema encoding'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'name': Schema{
|
||||
typ: 'string'
|
||||
typ: 'string'
|
||||
description: 'name of the test person'
|
||||
}
|
||||
'age': Schema{
|
||||
typ: 'integer'
|
||||
typ: 'integer'
|
||||
description: 'age of the test person'
|
||||
}
|
||||
}
|
||||
@@ -42,17 +42,17 @@ struct TestPerson {
|
||||
}'
|
||||
|
||||
schema := Schema{
|
||||
schema: 'test'
|
||||
title: 'TestPerson'
|
||||
schema: 'test'
|
||||
title: 'TestPerson'
|
||||
description: 'person struct used for test schema encoding'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'name': Schema{
|
||||
typ: 'string'
|
||||
typ: 'string'
|
||||
description: 'name of the test person'
|
||||
}
|
||||
'age': Schema{
|
||||
typ: 'integer'
|
||||
typ: 'integer'
|
||||
description: 'age of the test person'
|
||||
}
|
||||
'friend': Reference{
|
||||
@@ -67,30 +67,30 @@ struct TestPerson {
|
||||
|
||||
fn test_schema_to_structs_recursive() ! {
|
||||
schema := Schema{
|
||||
schema: 'test'
|
||||
title: 'TestPerson'
|
||||
schema: 'test'
|
||||
title: 'TestPerson'
|
||||
description: 'person struct used for test schema encoding'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'name': Schema{
|
||||
typ: 'string'
|
||||
typ: 'string'
|
||||
description: 'name of the test person'
|
||||
}
|
||||
'age': Schema{
|
||||
typ: 'integer'
|
||||
typ: 'integer'
|
||||
description: 'age of the test person'
|
||||
}
|
||||
'friend': Schema{
|
||||
title: 'TestFriend'
|
||||
typ: 'object'
|
||||
title: 'TestFriend'
|
||||
typ: 'object'
|
||||
description: 'friend of the test person'
|
||||
properties: {
|
||||
properties: {
|
||||
'name': Schema{
|
||||
typ: 'string'
|
||||
typ: 'string'
|
||||
description: 'name of the test friend person'
|
||||
}
|
||||
'age': Schema{
|
||||
typ: 'integer'
|
||||
typ: 'integer'
|
||||
description: 'age of the test friend person'
|
||||
}
|
||||
}
|
||||
@@ -99,4 +99,4 @@ fn test_schema_to_structs_recursive() ! {
|
||||
}
|
||||
encoded := schema_to_structs(schema)!
|
||||
log.debug(encoded.str())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module codegen
|
||||
|
||||
import freeflowuniverse.herolib.core.code { Param, Result, Struct, Type }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { SchemaRef, Schema, Reference, Number}
|
||||
import freeflowuniverse.herolib.core.code { Param, Struct, Type }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Number, Reference, Schema, SchemaRef }
|
||||
|
||||
// struct_to_schema generates a json schema or reference from a struct model
|
||||
pub fn sumtype_to_schema(sumtype code.Sumtype) SchemaRef {
|
||||
@@ -14,9 +14,9 @@ pub fn sumtype_to_schema(sumtype code.Sumtype) SchemaRef {
|
||||
title := sumtype.name
|
||||
|
||||
return SchemaRef(Schema{
|
||||
title: title
|
||||
title: title
|
||||
description: sumtype.description
|
||||
one_of: one_of
|
||||
one_of: one_of
|
||||
})
|
||||
}
|
||||
|
||||
@@ -47,9 +47,9 @@ pub fn struct_to_schema(struct_ Struct) SchemaRef {
|
||||
}
|
||||
|
||||
return SchemaRef(Schema{
|
||||
title: title
|
||||
title: title
|
||||
description: struct_.description
|
||||
properties: properties
|
||||
properties: properties
|
||||
})
|
||||
}
|
||||
|
||||
@@ -68,20 +68,20 @@ pub fn typesymbol_to_schema(symbol_ string) SchemaRef {
|
||||
} else if symbol.starts_with('[]') {
|
||||
mut array_type := symbol.trim_string_left('[]')
|
||||
return SchemaRef(Schema{
|
||||
typ: 'array'
|
||||
typ: 'array'
|
||||
items: typesymbol_to_schema(array_type)
|
||||
})
|
||||
} else if symbol.starts_with('map[string]') {
|
||||
mut map_type := symbol.trim_string_left('map[string]')
|
||||
return SchemaRef(Schema{
|
||||
typ: 'object'
|
||||
typ: 'object'
|
||||
additional_properties: typesymbol_to_schema(map_type)
|
||||
})
|
||||
} else if symbol[0].is_capital() {
|
||||
// todo: better imported type handling
|
||||
if symbol == 'Uint128' {
|
||||
return SchemaRef(Schema{
|
||||
typ: 'integer'
|
||||
typ: 'integer'
|
||||
minimum: Number(0)
|
||||
// todo: implement uint128 number
|
||||
// maximum: Number('340282366920938463463374607431768211455')
|
||||
@@ -170,20 +170,20 @@ pub fn type_to_schema(typ Type) SchemaRef {
|
||||
} else if symbol.starts_with('[]') {
|
||||
mut array_type := symbol.trim_string_left('[]')
|
||||
return SchemaRef(Schema{
|
||||
typ: 'array'
|
||||
typ: 'array'
|
||||
items: typesymbol_to_schema(array_type)
|
||||
})
|
||||
} else if symbol.starts_with('map[string]') {
|
||||
mut map_type := symbol.trim_string_left('map[string]')
|
||||
return SchemaRef(Schema{
|
||||
typ: 'object'
|
||||
typ: 'object'
|
||||
additional_properties: typesymbol_to_schema(map_type)
|
||||
})
|
||||
} else if symbol[0].is_capital() {
|
||||
// todo: better imported type handling
|
||||
if symbol == 'Uint128' {
|
||||
return SchemaRef(Schema{
|
||||
typ: 'integer'
|
||||
typ: 'integer'
|
||||
minimum: Number(0)
|
||||
// todo: implement uint128 number
|
||||
// maximum: Number('340282366920938463463374607431768211455')
|
||||
|
||||
@@ -5,13 +5,13 @@ import freeflowuniverse.herolib.core.code
|
||||
|
||||
fn test_struct_to_schema() {
|
||||
struct_ := code.Struct{
|
||||
name: 'test_name'
|
||||
name: 'test_name'
|
||||
description: 'a codemodel struct to test struct to schema serialization'
|
||||
fields: [
|
||||
fields: [
|
||||
code.StructField{
|
||||
name: 'test_field'
|
||||
name: 'test_field'
|
||||
description: 'a field of the test struct to test fields serialization into schema'
|
||||
typ: code.String{}
|
||||
typ: code.String{}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,77 +1,76 @@
|
||||
module jsonschema
|
||||
|
||||
|
||||
// Define numeric schemas
|
||||
const schema_u8 = Schema{
|
||||
typ: "integer"
|
||||
format: 'uint8'
|
||||
minimum: 0
|
||||
maximum: 255
|
||||
description: "An unsigned 8-bit integer."
|
||||
typ: 'integer'
|
||||
format: 'uint8'
|
||||
minimum: 0
|
||||
maximum: 255
|
||||
description: 'An unsigned 8-bit integer.'
|
||||
}
|
||||
|
||||
const schema_i8 = Schema{
|
||||
typ: "integer"
|
||||
format: 'int8'
|
||||
minimum: -128
|
||||
maximum: 127
|
||||
description: "A signed 8-bit integer."
|
||||
typ: 'integer'
|
||||
format: 'int8'
|
||||
minimum: -128
|
||||
maximum: 127
|
||||
description: 'A signed 8-bit integer.'
|
||||
}
|
||||
|
||||
const schema_u16 = Schema{
|
||||
typ: "integer"
|
||||
format: 'uint16'
|
||||
minimum: 0
|
||||
maximum: 65535
|
||||
description: "An unsigned 16-bit integer."
|
||||
typ: 'integer'
|
||||
format: 'uint16'
|
||||
minimum: 0
|
||||
maximum: 65535
|
||||
description: 'An unsigned 16-bit integer.'
|
||||
}
|
||||
|
||||
const schema_i16 = Schema{
|
||||
typ: "integer"
|
||||
format: 'int16'
|
||||
minimum: -32768
|
||||
maximum: 32767
|
||||
description: "A signed 16-bit integer."
|
||||
typ: 'integer'
|
||||
format: 'int16'
|
||||
minimum: -32768
|
||||
maximum: 32767
|
||||
description: 'A signed 16-bit integer.'
|
||||
}
|
||||
|
||||
const schema_u32 = Schema{
|
||||
typ: "integer"
|
||||
format: 'uint32'
|
||||
minimum: 0
|
||||
maximum: 4294967295
|
||||
description: "An unsigned 32-bit integer."
|
||||
typ: 'integer'
|
||||
format: 'uint32'
|
||||
minimum: 0
|
||||
maximum: 4294967295
|
||||
description: 'An unsigned 32-bit integer.'
|
||||
}
|
||||
|
||||
const schema_i32 = Schema{
|
||||
typ: "integer"
|
||||
format: 'int32'
|
||||
minimum: -2147483648
|
||||
maximum: 2147483647
|
||||
description: "A signed 32-bit integer."
|
||||
typ: 'integer'
|
||||
format: 'int32'
|
||||
minimum: -2147483648
|
||||
maximum: 2147483647
|
||||
description: 'A signed 32-bit integer.'
|
||||
}
|
||||
|
||||
const schema_u64 = Schema{
|
||||
typ: "integer"
|
||||
format: 'uint64'
|
||||
minimum: 0
|
||||
maximum: 18446744073709551615
|
||||
description: "An unsigned 64-bit integer."
|
||||
typ: 'integer'
|
||||
format: 'uint64'
|
||||
minimum: 0
|
||||
maximum: 18446744073709551615
|
||||
description: 'An unsigned 64-bit integer.'
|
||||
}
|
||||
|
||||
const schema_i64 = Schema{
|
||||
typ: "integer"
|
||||
format: 'int64'
|
||||
minimum: -9223372036854775808
|
||||
maximum: 9223372036854775807
|
||||
description: "A signed 64-bit integer."
|
||||
typ: 'integer'
|
||||
format: 'int64'
|
||||
minimum: -9223372036854775808
|
||||
maximum: 9223372036854775807
|
||||
description: 'A signed 64-bit integer.'
|
||||
}
|
||||
|
||||
const schema_f32 = Schema{
|
||||
typ: "number"
|
||||
description: "A 32-bit floating-point number."
|
||||
typ: 'number'
|
||||
description: 'A 32-bit floating-point number.'
|
||||
}
|
||||
|
||||
const schema_f64 = Schema{
|
||||
typ: "number"
|
||||
description: "A 64-bit floating-point number."
|
||||
}
|
||||
typ: 'number'
|
||||
description: 'A 64-bit floating-point number.'
|
||||
}
|
||||
|
||||
@@ -48,4 +48,4 @@ pub fn decode_schemaref(data_map map[string]Any) !SchemaRef {
|
||||
}
|
||||
}
|
||||
return decode(data_map.str())!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,34 +11,34 @@ struct Pet {
|
||||
|
||||
fn test_decode() ! {
|
||||
mut pet_schema_file := pathlib.get_file(
|
||||
path: '${jsonschema.testdata}/pet.json'
|
||||
path: '${testdata}/pet.json'
|
||||
)!
|
||||
pet_schema_str := pet_schema_file.read()!
|
||||
pet_schema := decode(pet_schema_str)!
|
||||
assert pet_schema == Schema{
|
||||
typ: 'object'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'name': Schema{
|
||||
typ: 'string'
|
||||
}
|
||||
}
|
||||
required: ['name']
|
||||
required: ['name']
|
||||
}
|
||||
}
|
||||
|
||||
fn test_decode_schemaref() ! {
|
||||
mut pet_schema_file := pathlib.get_file(
|
||||
path: '${jsonschema.testdata}/pet.json'
|
||||
path: '${testdata}/pet.json'
|
||||
)!
|
||||
pet_schema_str := pet_schema_file.read()!
|
||||
pet_schemaref := decode(pet_schema_str)!
|
||||
assert pet_schemaref == Schema{
|
||||
typ: 'object'
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'name': Schema{
|
||||
typ: 'string'
|
||||
}
|
||||
}
|
||||
required: ['name']
|
||||
required: ['name']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,62 +27,61 @@ pub type Number = int
|
||||
// See: https://json-schema.org/draft-07/json-schema-release-notes.html
|
||||
pub struct Schema {
|
||||
pub mut:
|
||||
// The $schema keyword identifies which version of JSON Schema the schema was written for
|
||||
schema string @[json: 'schema'; omitempty]
|
||||
|
||||
// The $id keyword defines a URI for the schema
|
||||
id string @[json: 'id'; omitempty]
|
||||
|
||||
// Human-readable title for the schema
|
||||
title string @[omitempty]
|
||||
|
||||
// Human-readable description of the schema
|
||||
description string @[omitempty]
|
||||
|
||||
// The data type for the schema (string, number, object, array, boolean, null)
|
||||
typ string @[json: 'type'; omitempty]
|
||||
|
||||
// Object properties when type is "object"
|
||||
properties map[string]SchemaRef @[omitempty]
|
||||
|
||||
// Controls additional properties not defined in the properties map
|
||||
additional_properties ?SchemaRef @[json: 'additionalProperties'; omitempty]
|
||||
|
||||
// List of required property names
|
||||
required []string @[omitempty]
|
||||
|
||||
// Schema for array items when type is "array"
|
||||
items ?Items @[omitempty]
|
||||
|
||||
// Definitions of reusable schemas
|
||||
defs map[string]SchemaRef @[omitempty]
|
||||
|
||||
// List of schemas, where data must validate against exactly one schema
|
||||
one_of []SchemaRef @[json: 'oneOf'; omitempty]
|
||||
|
||||
// Semantic format of the data (e.g., "date-time", "email", "uri")
|
||||
format string @[omitempty]
|
||||
|
||||
// === Validation for numbers ===
|
||||
|
||||
// The value must be a multiple of this number
|
||||
multiple_of int @[json: 'multipleOf'; omitempty]
|
||||
|
||||
// The maximum allowed value
|
||||
maximum int @[omitempty]
|
||||
|
||||
// The exclusive maximum allowed value (value must be less than, not equal to)
|
||||
exclusive_maximum int @[json: 'exclusiveMaximum'; omitempty]
|
||||
|
||||
// The minimum allowed value
|
||||
minimum int @[omitempty]
|
||||
|
||||
// The exclusive minimum allowed value (value must be greater than, not equal to)
|
||||
exclusive_minimum int @[json: 'exclusiveMinimum'; omitempty]
|
||||
|
||||
// Enumerated list of allowed values
|
||||
enum_ []string @[json: 'enum'; omitempty]
|
||||
|
||||
// Example value that would validate against this schema (not used for validation)
|
||||
example json.Any @[json: '-']
|
||||
}
|
||||
// The $schema keyword identifies which version of JSON Schema the schema was written for
|
||||
schema string @[json: 'schema'; omitempty]
|
||||
|
||||
// The $id keyword defines a URI for the schema
|
||||
id string @[json: 'id'; omitempty]
|
||||
|
||||
// Human-readable title for the schema
|
||||
title string @[omitempty]
|
||||
|
||||
// Human-readable description of the schema
|
||||
description string @[omitempty]
|
||||
|
||||
// The data type for the schema (string, number, object, array, boolean, null)
|
||||
typ string @[json: 'type'; omitempty]
|
||||
|
||||
// Object properties when type is "object"
|
||||
properties map[string]SchemaRef @[omitempty]
|
||||
|
||||
// Controls additional properties not defined in the properties map
|
||||
additional_properties ?SchemaRef @[json: 'additionalProperties'; omitempty]
|
||||
|
||||
// List of required property names
|
||||
required []string @[omitempty]
|
||||
|
||||
// Schema for array items when type is "array"
|
||||
items ?Items @[omitempty]
|
||||
|
||||
// Definitions of reusable schemas
|
||||
defs map[string]SchemaRef @[omitempty]
|
||||
|
||||
// List of schemas, where data must validate against exactly one schema
|
||||
one_of []SchemaRef @[json: 'oneOf'; omitempty]
|
||||
|
||||
// Semantic format of the data (e.g., "date-time", "email", "uri")
|
||||
format string @[omitempty]
|
||||
|
||||
// === Validation for numbers ===
|
||||
// The value must be a multiple of this number
|
||||
multiple_of int @[json: 'multipleOf'; omitempty]
|
||||
|
||||
// The maximum allowed value
|
||||
maximum int @[omitempty]
|
||||
|
||||
// The exclusive maximum allowed value (value must be less than, not equal to)
|
||||
exclusive_maximum int @[json: 'exclusiveMaximum'; omitempty]
|
||||
|
||||
// The minimum allowed value
|
||||
minimum int @[omitempty]
|
||||
|
||||
// The exclusive minimum allowed value (value must be greater than, not equal to)
|
||||
exclusive_minimum int @[json: 'exclusiveMinimum'; omitempty]
|
||||
|
||||
// Enumerated list of allowed values
|
||||
enum_ []string @[json: 'enum'; omitempty]
|
||||
|
||||
// Example value that would validate against this schema (not used for validation)
|
||||
example json.Any @[json: '-']
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
module codegen
|
||||
|
||||
import freeflowuniverse.herolib.core.code {Folder, File}
|
||||
import freeflowuniverse.herolib.core.code { File, Folder }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Schema, Reference }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen { schema_to_struct }
|
||||
import freeflowuniverse.herolib.schemas.openrpc.codegen as openrpc_codegen { content_descriptor_to_parameter }
|
||||
import freeflowuniverse.herolib.schemas.openrpc.codegen as openrpc_codegen
|
||||
import freeflowuniverse.herolib.schemas.openapi { OpenAPI, Operation }
|
||||
import freeflowuniverse.herolib.baobab.specification {ActorSpecification, ActorMethod, BaseObject}
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import net.http
|
||||
|
||||
// the body_generator is a function that takes an OpenAPI operation, its path, and its method
|
||||
// and returns a client function's body. This is used for custom client generation.
|
||||
pub type BodyGenerator = fn (openapi.Operation, string, http.Method) string
|
||||
pub type BodyGenerator = fn (op Operation, path string, method http.Method) string
|
||||
|
||||
fn generate_empty_body(op Operation, path string, method http.Method) string {
|
||||
return ''
|
||||
@@ -23,40 +23,35 @@ pub:
|
||||
// by default the TS Client Genrator generates empty bodies
|
||||
// for client methods
|
||||
custom_client_code string // custom code to be injected into client class
|
||||
body_generator BodyGenerator = generate_empty_body
|
||||
body_generator BodyGenerator = generate_empty_body
|
||||
}
|
||||
|
||||
pub fn ts_client_folder(spec OpenAPI, params GenerationParams) !code.Folder {
|
||||
pub fn ts_client_folder(spec OpenAPI, params GenerationParams) !Folder {
|
||||
schemas := spec.components.schemas.values()
|
||||
.map(
|
||||
if it is Reference { spec.dereference_schema(it)! }
|
||||
else { it as Schema }
|
||||
)
|
||||
|
||||
return Folder {
|
||||
name: 'client_typescript'
|
||||
.map(if it is Reference { spec.dereference_schema(it)! } else { it as Schema })
|
||||
|
||||
return Folder{
|
||||
name: 'client_typescript'
|
||||
files: [
|
||||
ts_client_model_file(schemas),
|
||||
ts_client_methods_file(spec, params)
|
||||
ts_client_methods_file(spec, params),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// generates a model.ts file for given base objects
|
||||
pub fn ts_client_model_file(schemas []Schema) File {
|
||||
return File {
|
||||
name: 'model'
|
||||
return File{
|
||||
name: 'model'
|
||||
extension: 'ts'
|
||||
content: schemas.map(schema_to_struct(it))
|
||||
.map(it.typescript())
|
||||
.join_lines()
|
||||
content: schemas.map(schema_to_struct(it))
|
||||
.map(it.typescript())
|
||||
.join_lines()
|
||||
}
|
||||
}
|
||||
|
||||
// generates a methods.ts file for given actor methods
|
||||
pub fn ts_client_methods_file(spec OpenAPI, params GenerationParams) File {
|
||||
|
||||
|
||||
// spec := spec_.validate()
|
||||
mut files := []File{}
|
||||
mut methods := []string{}
|
||||
@@ -66,11 +61,11 @@ pub fn ts_client_methods_file(spec OpenAPI, params GenerationParams) File {
|
||||
for path, item in spec.paths {
|
||||
if item.get.responses.len > 0 {
|
||||
methods << ts_client_fn(item.get, path, .get, params)
|
||||
}
|
||||
}
|
||||
if item.put.responses.len > 0 {
|
||||
methods << ts_client_fn(item.put, path, .put, params)
|
||||
}
|
||||
if item.post.responses.len > 0 {
|
||||
if item.post.responses.len > 0 {
|
||||
methods << ts_client_fn(item.post, path, .post, params)
|
||||
}
|
||||
if item.delete.responses.len > 0 {
|
||||
@@ -79,11 +74,11 @@ pub fn ts_client_methods_file(spec OpenAPI, params GenerationParams) File {
|
||||
}
|
||||
|
||||
client_code := 'export class ${texttools.pascal_case(spec.info.title)}Client {\n${params.custom_client_code}\n${methods.join_lines()}\n}'
|
||||
|
||||
return File {
|
||||
name: 'methods'
|
||||
|
||||
return File{
|
||||
name: 'methods'
|
||||
extension: 'ts'
|
||||
content: client_code
|
||||
content: client_code
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,8 +99,8 @@ fn ts_client_fn(op Operation, path string, method http.Method, gen GenerationPar
|
||||
}
|
||||
params << op.parameters.map(parameter_to_param(it))
|
||||
params_str := params.map(it.typescript()).join(', ')
|
||||
|
||||
|
||||
body := gen.body_generator(op, path, method)
|
||||
return_type := responses_to_param(op.responses).typ.typescript()
|
||||
return 'async ${name}(${params_str}): Promise<${return_type}> {${body}}'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
module codegen
|
||||
|
||||
import freeflowuniverse.herolib.core.code {Folder, File}
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.jsonschema {Schema, Reference, SchemaRef}
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen { schema_to_struct }
|
||||
import freeflowuniverse.herolib.schemas.openrpc.codegen as openrpc_codegen { content_descriptor_to_parameter }
|
||||
import freeflowuniverse.herolib.schemas.openapi { OpenAPI, ResponseSpec, Operation }
|
||||
import freeflowuniverse.herolib.baobab.specification {ActorSpecification, ActorMethod, BaseObject}
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema, SchemaRef }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen
|
||||
import freeflowuniverse.herolib.schemas.openrpc.codegen as openrpc_codegen
|
||||
import freeflowuniverse.herolib.schemas.openapi { Operation, ResponseSpec }
|
||||
import freeflowuniverse.herolib.baobab.specification
|
||||
import net.http
|
||||
|
||||
const test_operation = openapi.Operation{
|
||||
summary: 'List all pets'
|
||||
const test_operation = Operation{
|
||||
summary: 'List all pets'
|
||||
operation_id: 'listPets'
|
||||
parameters: [
|
||||
parameters: [
|
||||
openapi.Parameter{
|
||||
name: 'limit'
|
||||
in_: 'query'
|
||||
name: 'limit'
|
||||
in_: 'query'
|
||||
description: 'Maximum number of pets to return'
|
||||
required: false
|
||||
schema: Schema{
|
||||
typ: 'integer'
|
||||
required: false
|
||||
schema: Schema{
|
||||
typ: 'integer'
|
||||
format: 'int32'
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
responses: {
|
||||
responses: {
|
||||
'200': ResponseSpec{
|
||||
description: 'A paginated list of pets'
|
||||
content: {
|
||||
content: {
|
||||
'application/json': openapi.MediaType{
|
||||
schema: Schema{
|
||||
typ: "array",
|
||||
typ: 'array'
|
||||
items: SchemaRef(Reference{
|
||||
ref: "#/components/schemas/Pet"
|
||||
ref: '#/components/schemas/Pet'
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -55,4 +55,4 @@ fn test_ts_client_fn() {
|
||||
|
||||
fn body_generator(op Operation, path string, method http.Method) string {
|
||||
return 'console.log("implement")'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
module codegen
|
||||
|
||||
import freeflowuniverse.herolib.core.code
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen {schemaref_to_type}
|
||||
import freeflowuniverse.herolib.schemas.openapi {ResponseSpec}
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen { schemaref_to_type }
|
||||
import freeflowuniverse.herolib.schemas.openapi { ResponseSpec }
|
||||
|
||||
// converts OpenAPI Parameter Specification
|
||||
// converts OpenAPI Parameter Specification
|
||||
// to code param specification
|
||||
pub fn media_type_to_param(mt openapi.MediaType) code.Param {
|
||||
return code.Param {
|
||||
return code.Param{
|
||||
name: 'data'
|
||||
typ: schemaref_to_type(mt.schema)
|
||||
typ: schemaref_to_type(mt.schema)
|
||||
}
|
||||
}
|
||||
|
||||
// converts OpenAPI Parameter Specification
|
||||
// converts OpenAPI Parameter Specification
|
||||
// to code param specification
|
||||
pub fn parameter_to_param(parameter openapi.Parameter) code.Param {
|
||||
return code.Param {
|
||||
name: parameter.name
|
||||
typ: schemaref_to_type(parameter.schema)
|
||||
return code.Param{
|
||||
name: parameter.name
|
||||
typ: schemaref_to_type(parameter.schema)
|
||||
description: parameter.description
|
||||
}
|
||||
}
|
||||
|
||||
// converts OpenAPI map[string]ResponseSpec Specification
|
||||
// converts OpenAPI map[string]ResponseSpec Specification
|
||||
// to code param specification
|
||||
pub fn responses_to_param(responses map[string]ResponseSpec) code.Param {
|
||||
response_type := if '200' in responses {
|
||||
@@ -35,11 +35,13 @@ pub fn responses_to_param(responses map[string]ResponseSpec) code.Param {
|
||||
} else {
|
||||
code.Void{}
|
||||
}
|
||||
|
||||
return code.Param {
|
||||
|
||||
return code.Param{
|
||||
name: 'ok'
|
||||
typ: if responses.errors().len > 0 {
|
||||
typ: if responses.errors().len > 0 {
|
||||
code.Result{response_type}
|
||||
} else { response_type }
|
||||
} else {
|
||||
response_type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,238 +1,236 @@
|
||||
module openapi
|
||||
|
||||
import veb
|
||||
import freeflowuniverse.herolib.schemas.jsonschema {Schema}
|
||||
import x.json2 {Any}
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Schema }
|
||||
import x.json2 { Any }
|
||||
import net.http
|
||||
import os
|
||||
|
||||
const templates = os.join_path(os.dir(@FILE), 'templates')
|
||||
|
||||
pub struct HTTPController {
|
||||
veb.StaticHandler
|
||||
Handler // Handles OpenAPI requests
|
||||
veb.StaticHandler
|
||||
Handler // Handles OpenAPI requests
|
||||
pub:
|
||||
base_url string
|
||||
specification OpenAPI
|
||||
specification_path string
|
||||
base_url string
|
||||
specification OpenAPI
|
||||
specification_path string
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
veb.Context
|
||||
}
|
||||
|
||||
// Creates a new HTTPController instance
|
||||
pub fn new_http_controller(c HTTPController) !&HTTPController {
|
||||
mut ctrl := HTTPController{
|
||||
...c,
|
||||
Handler: c.Handler
|
||||
}
|
||||
...c
|
||||
Handler: c.Handler
|
||||
}
|
||||
|
||||
if c.specification_path != '' {
|
||||
if !os.exists(c.specification_path) {
|
||||
return error('OpenAPI Specification not found in path.')
|
||||
}
|
||||
ctrl.serve_static('/openapi.json', c.specification_path)!
|
||||
}
|
||||
return &ctrl
|
||||
if c.specification_path != '' {
|
||||
if !os.exists(c.specification_path) {
|
||||
return error('OpenAPI Specification not found in path.')
|
||||
}
|
||||
ctrl.serve_static('/openapi.json', c.specification_path)!
|
||||
}
|
||||
return &ctrl
|
||||
}
|
||||
|
||||
pub fn (mut c HTTPController) index(mut ctx Context) veb.Result {
|
||||
return ctx.html($tmpl('templates/swagger.html'))
|
||||
return ctx.html($tmpl('templates/swagger.html'))
|
||||
}
|
||||
|
||||
@['/:path...'; get; post; put; delete; patch]
|
||||
@['/:path...'; delete; get; patch; post; put]
|
||||
pub fn (mut c HTTPController) endpoints(mut ctx Context, path string) veb.Result {
|
||||
println('Requested path: $path')
|
||||
println('Requested path: ${path}')
|
||||
|
||||
// Extract the HTTP method
|
||||
method := ctx.req.method.str().to_lower()
|
||||
// Extract the HTTP method
|
||||
method := ctx.req.method.str().to_lower()
|
||||
|
||||
// Matches the request path against the OpenAPI specification and retrieves the corresponding PathItem
|
||||
path_item := match_path(path, c.specification) or {
|
||||
// Return a 404 error if no matching path is found
|
||||
return ctx.not_found()
|
||||
}
|
||||
// Matches the request path against the OpenAPI specification and retrieves the corresponding PathItem
|
||||
path_item := match_path(path, c.specification) or {
|
||||
// Return a 404 error if no matching path is found
|
||||
return ctx.not_found()
|
||||
}
|
||||
|
||||
// // Check if the path exists in the OpenAPI specification
|
||||
// path_item := c.specification.paths[path] or {
|
||||
// // Return a 404 error if the path is not defined
|
||||
// return ctx.not_found()
|
||||
// }
|
||||
|
||||
// // Check if the path exists in the OpenAPI specification
|
||||
// path_item := c.specification.paths[path] or {
|
||||
// // Return a 404 error if the path is not defined
|
||||
// return ctx.not_found()
|
||||
// }
|
||||
// Match the HTTP method with the OpenAPI specification
|
||||
operation := match method {
|
||||
'get' {
|
||||
path_item.get
|
||||
}
|
||||
'post' {
|
||||
path_item.post
|
||||
}
|
||||
'put' {
|
||||
path_item.put
|
||||
}
|
||||
'delete' {
|
||||
path_item.delete
|
||||
}
|
||||
'patch' {
|
||||
path_item.patch
|
||||
}
|
||||
else {
|
||||
// Return 405 Method Not Allowed if the method is not supported
|
||||
return ctx.method_not_allowed()
|
||||
}
|
||||
}
|
||||
|
||||
// Match the HTTP method with the OpenAPI specification
|
||||
operation := match method {
|
||||
'get' { path_item.get }
|
||||
'post' { path_item.post }
|
||||
'put' { path_item.put }
|
||||
'delete' { path_item.delete }
|
||||
'patch' { path_item.patch }
|
||||
else {
|
||||
// Return 405 Method Not Allowed if the method is not supported
|
||||
return ctx.method_not_allowed()
|
||||
}
|
||||
}
|
||||
mut arg_map := map[string]Any{}
|
||||
path_arg := path.all_after_last('/')
|
||||
// the OpenAPI Parameter specification belonging to the path argument
|
||||
arg_params := operation.parameters.filter(it.in_ == 'path')
|
||||
if arg_params.len > 1 {
|
||||
// TODO: use path template to support multiple arguments (right now just last arg supported)
|
||||
panic('implement')
|
||||
} else if arg_params.len == 1 {
|
||||
arg_map[arg_params[0].name] = arg_params[0].typed(path_arg)
|
||||
}
|
||||
|
||||
mut parameters := ctx.query.clone()
|
||||
// Build the Request object
|
||||
request := Request{
|
||||
path: path
|
||||
operation: operation
|
||||
method: method
|
||||
arguments: arg_map
|
||||
parameters: parameters
|
||||
body: ctx.req.data
|
||||
header: ctx.req.header
|
||||
}
|
||||
|
||||
mut arg_map := map[string]Any
|
||||
path_arg := path.all_after_last('/')
|
||||
// the OpenAPI Parameter specification belonging to the path argument
|
||||
arg_params := operation.parameters.filter(it.in_ == 'path')
|
||||
if arg_params.len > 1 {
|
||||
// TODO: use path template to support multiple arguments (right now just last arg supported)
|
||||
panic('implement')
|
||||
} else if arg_params.len == 1 {
|
||||
arg_map[arg_params[0].name] = arg_params[0].typed(path_arg)
|
||||
}
|
||||
// Use the handler to process the request
|
||||
response := c.handler.handle(request) or {
|
||||
// Use OpenAPI spec to determine the response status for the error
|
||||
return ctx.handle_error(operation.responses, err)
|
||||
}
|
||||
|
||||
mut parameters := ctx.query.clone()
|
||||
// Build the Request object
|
||||
request := Request{
|
||||
path: path
|
||||
operation: operation
|
||||
method: method
|
||||
arguments: arg_map
|
||||
parameters: parameters
|
||||
body: ctx.req.data
|
||||
header: ctx.req.header
|
||||
}
|
||||
// Return the response to the client
|
||||
ctx.res.set_status(response.status)
|
||||
|
||||
// Use the handler to process the request
|
||||
response := c.handler.handle(request) or {
|
||||
// Use OpenAPI spec to determine the response status for the error
|
||||
return ctx.handle_error(operation.responses, err)
|
||||
}
|
||||
|
||||
// Return the response to the client
|
||||
ctx.res.set_status(response.status)
|
||||
|
||||
// ctx.res.header = response.header
|
||||
// ctx.set_content_type('application/json')
|
||||
// ctx.res.header = response.header
|
||||
// ctx.set_content_type('application/json')
|
||||
|
||||
// return ctx.ok('[]')
|
||||
return ctx.send_response_to_client('application/json', response.body)
|
||||
return ctx.send_response_to_client('application/json', response.body)
|
||||
}
|
||||
|
||||
// Handles errors and maps them to OpenAPI-defined response statuses
|
||||
fn (mut ctx Context) handle_error(possible_responses map[string]ResponseSpec, err IError) veb.Result {
|
||||
// Match the error with the defined responses
|
||||
for code, _ in possible_responses {
|
||||
if matches_error_to_status(err, code.int()) {
|
||||
ctx.res.set_status(http.status_from_int(code.int()))
|
||||
ctx.set_content_type('application/json')
|
||||
return ctx.send_response_to_client(
|
||||
'application/json',
|
||||
'{"error": "$err.msg()", "status": $code}'
|
||||
)
|
||||
}
|
||||
}
|
||||
// Match the error with the defined responses
|
||||
for code, _ in possible_responses {
|
||||
if matches_error_to_status(err, code.int()) {
|
||||
ctx.res.set_status(http.status_from_int(code.int()))
|
||||
ctx.set_content_type('application/json')
|
||||
return ctx.send_response_to_client('application/json', '{"error": "${err.msg()}", "status": ${code}}')
|
||||
}
|
||||
}
|
||||
|
||||
// Default to 500 Internal HTTPController Error if no match is found
|
||||
return ctx.server_error(
|
||||
'{"error": "Internal HTTPController Error", "status": 500}'
|
||||
)
|
||||
// Default to 500 Internal HTTPController Error if no match is found
|
||||
return ctx.server_error('{"error": "Internal HTTPController Error", "status": 500}')
|
||||
}
|
||||
|
||||
// Helper for 405 Method Not Allowed response
|
||||
fn (mut ctx Context) method_not_allowed() veb.Result {
|
||||
ctx.res.set_status(.method_not_allowed)
|
||||
ctx.set_content_type('application/json')
|
||||
return ctx.send_response_to_client(
|
||||
'application/json',
|
||||
'{"error": "Method Not Allowed", "status": 405}'
|
||||
)
|
||||
ctx.res.set_status(.method_not_allowed)
|
||||
ctx.set_content_type('application/json')
|
||||
return ctx.send_response_to_client('application/json', '{"error": "Method Not Allowed", "status": 405}')
|
||||
}
|
||||
|
||||
|
||||
// Matches a request path against OpenAPI path templates in the parsed structs
|
||||
// Returns the matching path key and corresponding PathItem if found
|
||||
fn match_path(req_path string, spec OpenAPI) !PathItem {
|
||||
// Iterate through all paths in the OpenAPI specification
|
||||
for template, path_item in spec.paths {
|
||||
if is_path_match(req_path, template) {
|
||||
// Return the matching path template and its PathItem
|
||||
return path_item
|
||||
}
|
||||
}
|
||||
// If no match is found, return an error
|
||||
return error('Path not found')
|
||||
// Iterate through all paths in the OpenAPI specification
|
||||
for template, path_item in spec.paths {
|
||||
if is_path_match(req_path, template) {
|
||||
// Return the matching path template and its PathItem
|
||||
return path_item
|
||||
}
|
||||
}
|
||||
// If no match is found, return an error
|
||||
return error('Path not found')
|
||||
}
|
||||
|
||||
// Helper to match an error to a specific response status
|
||||
fn matches_error_to_status(err IError, status int) bool {
|
||||
// This can be customized to map specific errors to statuses
|
||||
// For simplicity, we'll use a direct comparison here.
|
||||
return err.code() == status
|
||||
// This can be customized to map specific errors to statuses
|
||||
// For simplicity, we'll use a direct comparison here.
|
||||
return err.code() == status
|
||||
}
|
||||
|
||||
// Checks if a request path matches a given OpenAPI path template
|
||||
// Allows for dynamic path segments like `{petId}` in templates
|
||||
fn is_path_match(req_path string, template string) bool {
|
||||
// Split the request path and template into segments
|
||||
req_segments := req_path.split('/')
|
||||
template_segments := template.split('/')
|
||||
// Split the request path and template into segments
|
||||
req_segments := req_path.split('/')
|
||||
template_segments := template.split('/')
|
||||
|
||||
// If the number of segments doesn't match, the paths can't match
|
||||
if req_segments.len != template_segments.len {
|
||||
return false
|
||||
}
|
||||
// If the number of segments doesn't match, the paths can't match
|
||||
if req_segments.len != template_segments.len {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compare each segment in the template and request path
|
||||
for i, segment in template_segments {
|
||||
// If the segment is not dynamic (doesn't start with `{`), ensure it matches exactly
|
||||
if !segment.starts_with('{') && segment != req_segments[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// If all segments match or dynamic segments are valid, return true
|
||||
return true
|
||||
// Compare each segment in the template and request path
|
||||
for i, segment in template_segments {
|
||||
// If the segment is not dynamic (doesn't start with `{`), ensure it matches exactly
|
||||
if !segment.starts_with('{') && segment != req_segments[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// If all segments match or dynamic segments are valid, return true
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
pub fn (param Parameter) typed(value string) Any {
|
||||
param_schema := param.schema as Schema
|
||||
param_type := param_schema.typ
|
||||
param_format := param_schema.format
|
||||
param_schema := param.schema as Schema
|
||||
param_type := param_schema.typ
|
||||
param_format := param_schema.format
|
||||
|
||||
// Convert parameter value to corresponding type
|
||||
typ := match param_type {
|
||||
'integer' {
|
||||
param_format
|
||||
}
|
||||
'number' {
|
||||
param_format
|
||||
}
|
||||
else {
|
||||
param_type // Leave as param type for unknown types
|
||||
}
|
||||
}
|
||||
return typed(value, typ)
|
||||
// Convert parameter value to corresponding type
|
||||
typ := match param_type {
|
||||
'integer' {
|
||||
param_format
|
||||
}
|
||||
'number' {
|
||||
param_format
|
||||
}
|
||||
else {
|
||||
param_type // Leave as param type for unknown types
|
||||
}
|
||||
}
|
||||
return typed(value, typ)
|
||||
}
|
||||
|
||||
// typed gets a value that is string and a desired type, and returns the typed string in Any Type.
|
||||
pub fn typed(value string, typ string) Any {
|
||||
match typ {
|
||||
'int32' {
|
||||
return value.int() // Convert to int
|
||||
}
|
||||
'int64' {
|
||||
return value.i64() // Convert to i64
|
||||
}
|
||||
'string' {
|
||||
return value // Already a string
|
||||
}
|
||||
'boolean' {
|
||||
return value.bool() // Convert to bool
|
||||
}
|
||||
'float' {
|
||||
return value.f32() // Convert to float
|
||||
}
|
||||
'double' {
|
||||
return value.f64() // Convert to double
|
||||
}
|
||||
else {
|
||||
return value.f64() // Leave as string for unknown types
|
||||
}
|
||||
}
|
||||
}
|
||||
match typ {
|
||||
'int32' {
|
||||
return value.int() // Convert to int
|
||||
}
|
||||
'int64' {
|
||||
return value.i64() // Convert to i64
|
||||
}
|
||||
'string' {
|
||||
return value // Already a string
|
||||
}
|
||||
'boolean' {
|
||||
return value.bool() // Convert to bool
|
||||
}
|
||||
'float' {
|
||||
return value.f32() // Convert to float
|
||||
}
|
||||
'double' {
|
||||
return value.f64() // Convert to double
|
||||
}
|
||||
else {
|
||||
return value.f64() // Leave as string for unknown types
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
module openapi
|
||||
|
||||
import veb
|
||||
import freeflowuniverse.herolib.schemas.jsonschema {Schema}
|
||||
import x.json2 {Any}
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import x.json2
|
||||
import net.http
|
||||
import os
|
||||
|
||||
pub struct PlaygroundController {
|
||||
veb.StaticHandler
|
||||
veb.StaticHandler
|
||||
pub:
|
||||
base_url string
|
||||
specification_path string
|
||||
base_url string
|
||||
specification_path string
|
||||
}
|
||||
|
||||
// Creates a new HTTPController instance
|
||||
pub fn new_playground_controller(c PlaygroundController) !&PlaygroundController {
|
||||
mut ctrl := PlaygroundController{
|
||||
...c,
|
||||
}
|
||||
...c
|
||||
}
|
||||
|
||||
if c.specification_path != '' {
|
||||
if !os.exists(c.specification_path) {
|
||||
return error('OpenAPI Specification not found in path.')
|
||||
}
|
||||
ctrl.serve_static('/openapi.json', c.specification_path)!
|
||||
}
|
||||
return &ctrl
|
||||
if c.specification_path != '' {
|
||||
if !os.exists(c.specification_path) {
|
||||
return error('OpenAPI Specification not found in path.')
|
||||
}
|
||||
ctrl.serve_static('/openapi.json', c.specification_path)!
|
||||
}
|
||||
return &ctrl
|
||||
}
|
||||
|
||||
pub fn (mut c PlaygroundController) index(mut ctx Context) veb.Result {
|
||||
return ctx.html($tmpl('templates/swagger.html'))
|
||||
}
|
||||
return ctx.html($tmpl('templates/swagger.html'))
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
module openapi
|
||||
|
||||
import json
|
||||
import x.json2 {Any}
|
||||
import x.json2 { Any }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
|
||||
|
||||
|
||||
pub fn json_decode(data string) !OpenAPI {
|
||||
// Decode the raw JSON into a map to allow field-specific processing
|
||||
raw_map := json2.raw_decode(data)!.as_map()
|
||||
@@ -33,7 +31,7 @@ pub fn json_decode(data string) !OpenAPI {
|
||||
|
||||
pub fn json_decode_components(components_ Components, components_map map[string]Any) !Components {
|
||||
mut components := components_
|
||||
|
||||
|
||||
if schemas_any := components_map['schemas'] {
|
||||
components.schemas = jsonschema.decode_schemaref_map(schemas_any.as_map())!
|
||||
}
|
||||
@@ -42,11 +40,9 @@ pub fn json_decode_components(components_ Components, components_map map[string]
|
||||
|
||||
pub fn json_decode_path(path_ PathItem, path_map map[string]Any) !PathItem {
|
||||
mut path := path_
|
||||
|
||||
|
||||
for key in path_map.keys() {
|
||||
operation_any := path_map[key] or {
|
||||
panic('This should never happen')
|
||||
}
|
||||
operation_any := path_map[key] or { panic('This should never happen') }
|
||||
operation_map := operation_any.as_map()
|
||||
match key {
|
||||
'get' {
|
||||
@@ -87,20 +83,20 @@ pub fn json_decode_operation(operation_ Operation, operation_map map[string]Any)
|
||||
request_body_map := request_body_any.as_map()
|
||||
if content_any := request_body_map['content'] {
|
||||
mut request_body := json.decode(RequestBody, request_body_any.str())!
|
||||
// mut request_body := operation.request_body as RequestBody
|
||||
// mut request_body := operation.request_body as RequestBody
|
||||
mut content := request_body.content.clone()
|
||||
content_map := content_any.as_map()
|
||||
request_body.content = json_decode_content(content, content_map)!
|
||||
operation.request_body = request_body
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if responses_any := operation_map['responses'] {
|
||||
responses_map := responses_any.as_map()
|
||||
for key, response_any in responses_map {
|
||||
response_map := response_any.as_map()
|
||||
if content_any := response_map['content'] {
|
||||
mut response := operation.responses[key]
|
||||
mut response := operation.responses[key]
|
||||
mut content := response.content.clone()
|
||||
content_map := content_any.as_map()
|
||||
response.content = json_decode_content(content, content_map)!
|
||||
@@ -170,7 +166,6 @@ fn json_decode_content(content_ map[string]MediaType, content_map map[string]Any
|
||||
// obj.$(field.name) = arr
|
||||
// }
|
||||
|
||||
|
||||
// println('field ${field.name} ${typeof(field.typ)}')
|
||||
// field_map := data_map[field.name].as_map()
|
||||
// // Check if the field is of type Schema or SchemaRef
|
||||
@@ -210,7 +205,7 @@ pub fn (o OpenAPI) encode_json() string {
|
||||
joint << split[i]
|
||||
break
|
||||
}
|
||||
if split[i+1].trim_space().starts_with('"_type"') {
|
||||
if split[i + 1].trim_space().starts_with('"_type"') {
|
||||
if !split[i].trim_space().starts_with('"_type"') {
|
||||
joint << split[i].trim_string_right(',')
|
||||
}
|
||||
@@ -220,6 +215,6 @@ pub fn (o OpenAPI) encode_json() string {
|
||||
}
|
||||
joint << split[i]
|
||||
}
|
||||
|
||||
|
||||
return joint.join_lines()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
module openapi
|
||||
|
||||
import os
|
||||
import x.json2 {Any}
|
||||
import freeflowuniverse.herolib.schemas.jsonschema {Schema, Reference, SchemaRef}
|
||||
import x.json2 { Any }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema, SchemaRef }
|
||||
|
||||
const spec_path = '${os.dir(@FILE)}/testdata/openapi.json'
|
||||
const spec_json = os.read_file(spec_path) or {panic(err)}
|
||||
const spec_json = os.read_file(spec_path) or { panic(err) }
|
||||
|
||||
const spec = openapi.OpenAPI{
|
||||
openapi: '3.0.3'
|
||||
info: openapi.Info{
|
||||
title: 'Pet Store API'
|
||||
const spec = OpenAPI{
|
||||
openapi: '3.0.3'
|
||||
info: Info{
|
||||
title: 'Pet Store API'
|
||||
description: 'A sample API for a pet store'
|
||||
version: '1.0.0'
|
||||
}
|
||||
servers: [
|
||||
servers: [
|
||||
ServerSpec{
|
||||
url: 'https://api.petstore.example.com/v1'
|
||||
url: 'https://api.petstore.example.com/v1'
|
||||
description: 'Production server'
|
||||
},
|
||||
ServerSpec{
|
||||
url: 'https://staging.petstore.example.com/v1'
|
||||
url: 'https://staging.petstore.example.com/v1'
|
||||
description: 'Staging server'
|
||||
},
|
||||
]
|
||||
@@ -30,7 +30,7 @@ const spec = openapi.OpenAPI{
|
||||
summary: 'List all pets'
|
||||
operation_id: 'listPets'
|
||||
parameters: [
|
||||
openapi.Parameter{
|
||||
Parameter{
|
||||
name: 'limit'
|
||||
in_: 'query'
|
||||
description: 'Maximum number of pets to return'
|
||||
@@ -41,14 +41,14 @@ const spec = openapi.OpenAPI{
|
||||
}
|
||||
},
|
||||
]
|
||||
responses: {
|
||||
responses: {
|
||||
'200': ResponseSpec{
|
||||
description: 'A paginated list of pets'
|
||||
content: {
|
||||
'application/json': openapi.MediaType{
|
||||
schema: Reference{
|
||||
'application/json': MediaType{
|
||||
schema: Reference{
|
||||
ref: '#/components/schemas/Pets'
|
||||
},
|
||||
}
|
||||
example: Any('[{"id":"1","name":"Alice","email":"alice@example.com"},{"id":"2","name":"Bob","email":"bob@example.com"}]')
|
||||
}
|
||||
}
|
||||
@@ -61,21 +61,21 @@ const spec = openapi.OpenAPI{
|
||||
post: Operation{
|
||||
summary: 'Create a new pet'
|
||||
operation_id: 'createPet'
|
||||
request_body: openapi.RequestBody{
|
||||
request_body: RequestBody{
|
||||
required: true
|
||||
content: {
|
||||
'application/json': openapi.MediaType{
|
||||
'application/json': MediaType{
|
||||
schema: Reference{
|
||||
ref: '#/components/schemas/NewPet'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
responses: {
|
||||
responses: {
|
||||
'201': ResponseSpec{
|
||||
description: 'Pet created'
|
||||
content: {
|
||||
'application/json': openapi.MediaType{
|
||||
'application/json': MediaType{
|
||||
schema: Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
}
|
||||
@@ -93,7 +93,7 @@ const spec = openapi.OpenAPI{
|
||||
summary: 'Get a pet by ID'
|
||||
operation_id: 'getPet'
|
||||
parameters: [
|
||||
openapi.Parameter{
|
||||
Parameter{
|
||||
name: 'petId'
|
||||
in_: 'path'
|
||||
description: 'ID of the pet to retrieve'
|
||||
@@ -104,11 +104,11 @@ const spec = openapi.OpenAPI{
|
||||
}
|
||||
},
|
||||
]
|
||||
responses: {
|
||||
responses: {
|
||||
'200': ResponseSpec{
|
||||
description: 'A pet'
|
||||
content: {
|
||||
'application/json': openapi.MediaType{
|
||||
'application/json': MediaType{
|
||||
schema: Reference{
|
||||
ref: '#/components/schemas/Pet'
|
||||
}
|
||||
@@ -124,7 +124,7 @@ const spec = openapi.OpenAPI{
|
||||
summary: 'Delete a pet by ID'
|
||||
operation_id: 'deletePet'
|
||||
parameters: [
|
||||
openapi.Parameter{
|
||||
Parameter{
|
||||
name: 'petId'
|
||||
in_: 'path'
|
||||
description: 'ID of the pet to delete'
|
||||
@@ -135,7 +135,7 @@ const spec = openapi.OpenAPI{
|
||||
}
|
||||
},
|
||||
]
|
||||
responses: {
|
||||
responses: {
|
||||
'204': ResponseSpec{
|
||||
description: 'Pet deleted'
|
||||
}
|
||||
@@ -149,11 +149,11 @@ const spec = openapi.OpenAPI{
|
||||
get: Operation{
|
||||
summary: 'List all orders'
|
||||
operation_id: 'listOrders'
|
||||
responses: {
|
||||
responses: {
|
||||
'200': ResponseSpec{
|
||||
description: 'A list of orders'
|
||||
content: {
|
||||
'application/json': openapi.MediaType{
|
||||
'application/json': MediaType{
|
||||
schema: Schema{
|
||||
typ: 'array'
|
||||
items: SchemaRef(Reference{
|
||||
@@ -171,7 +171,7 @@ const spec = openapi.OpenAPI{
|
||||
summary: 'Get an order by ID'
|
||||
operation_id: 'getOrder'
|
||||
parameters: [
|
||||
openapi.Parameter{
|
||||
Parameter{
|
||||
name: 'orderId'
|
||||
in_: 'path'
|
||||
description: 'ID of the order to retrieve'
|
||||
@@ -182,11 +182,11 @@ const spec = openapi.OpenAPI{
|
||||
}
|
||||
},
|
||||
]
|
||||
responses: {
|
||||
responses: {
|
||||
'200': ResponseSpec{
|
||||
description: 'An order'
|
||||
content: {
|
||||
'application/json': openapi.MediaType{
|
||||
'application/json': MediaType{
|
||||
schema: Reference{
|
||||
ref: '#/components/schemas/Order'
|
||||
}
|
||||
@@ -202,7 +202,7 @@ const spec = openapi.OpenAPI{
|
||||
summary: 'Delete an order by ID'
|
||||
operation_id: 'deleteOrder'
|
||||
parameters: [
|
||||
openapi.Parameter{
|
||||
Parameter{
|
||||
name: 'orderId'
|
||||
in_: 'path'
|
||||
description: 'ID of the order to delete'
|
||||
@@ -213,7 +213,7 @@ const spec = openapi.OpenAPI{
|
||||
}
|
||||
},
|
||||
]
|
||||
responses: {
|
||||
responses: {
|
||||
'204': ResponseSpec{
|
||||
description: 'Order deleted'
|
||||
}
|
||||
@@ -227,21 +227,21 @@ const spec = openapi.OpenAPI{
|
||||
post: Operation{
|
||||
summary: 'Create a user'
|
||||
operation_id: 'createUser'
|
||||
request_body: openapi.RequestBody{
|
||||
request_body: RequestBody{
|
||||
required: true
|
||||
content: {
|
||||
'application/json': openapi.MediaType{
|
||||
'application/json': MediaType{
|
||||
schema: Reference{
|
||||
ref: '#/components/schemas/NewUser'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
responses: {
|
||||
responses: {
|
||||
'201': ResponseSpec{
|
||||
description: 'User created'
|
||||
content: {
|
||||
'application/json': openapi.MediaType{
|
||||
'application/json': MediaType{
|
||||
schema: Reference{
|
||||
ref: '#/components/schemas/User'
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module openapi
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonschema {Schema, SchemaRef, Reference}
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema, SchemaRef }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import os
|
||||
import maps
|
||||
@@ -8,9 +8,9 @@ import maps
|
||||
@[params]
|
||||
pub struct Params {
|
||||
pub:
|
||||
path string // path to openrpc.json file
|
||||
text string // content of openrpc specification text
|
||||
process bool // whether to process spec
|
||||
path string // path to openrpc.json file
|
||||
text string // content of openrpc specification text
|
||||
process bool // whether to process spec
|
||||
}
|
||||
|
||||
pub fn new(params Params) !OpenAPI {
|
||||
@@ -23,13 +23,17 @@ pub fn new(params Params) !OpenAPI {
|
||||
}
|
||||
|
||||
text := if params.path != '' {
|
||||
os.read_file(params.path)!
|
||||
} else { params.text }
|
||||
os.read_file(params.path)!
|
||||
} else {
|
||||
params.text
|
||||
}
|
||||
|
||||
specification := json_decode(text)!
|
||||
return if params.process {
|
||||
process(specification)!
|
||||
} else {specification}
|
||||
} else {
|
||||
specification
|
||||
}
|
||||
}
|
||||
|
||||
@[params]
|
||||
@@ -39,40 +43,53 @@ pub:
|
||||
}
|
||||
|
||||
pub fn process_schema(schema Schema, params ProcessSchema) Schema {
|
||||
return Schema {
|
||||
return Schema{
|
||||
...schema
|
||||
id: if schema.id != '' && schema.typ == 'object' { schema.id }
|
||||
else if schema.title != '' { schema.title }
|
||||
else { params.name }
|
||||
title: if schema.title != '' && schema.typ == 'object' { schema.title }
|
||||
else if schema.id != '' { schema.id }
|
||||
else { params.name }
|
||||
properties: maps.to_map[string, SchemaRef, string, SchemaRef](schema.properties, fn (k string, v SchemaRef) (string, SchemaRef) {
|
||||
return k, if v is Schema {SchemaRef(process_schema(v))} else {v}
|
||||
id: if schema.id != '' && schema.typ == 'object' {
|
||||
schema.id
|
||||
} else if schema.title != '' {
|
||||
schema.title
|
||||
} else {
|
||||
params.name
|
||||
}
|
||||
title: if schema.title != '' && schema.typ == 'object' {
|
||||
schema.title
|
||||
} else if schema.id != '' {
|
||||
schema.id
|
||||
} else {
|
||||
params.name
|
||||
}
|
||||
properties: maps.to_map[string, SchemaRef, string, SchemaRef](schema.properties,
|
||||
fn (k string, v SchemaRef) (string, SchemaRef) {
|
||||
return k, if v is Schema { SchemaRef(process_schema(v)) } else { v }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(spec OpenAPI) !OpenAPI {
|
||||
mut processed := OpenAPI{...spec
|
||||
mut processed := OpenAPI{
|
||||
...spec
|
||||
paths: spec.paths.clone()
|
||||
}
|
||||
|
||||
for key, schema in spec.components.schemas {
|
||||
if schema is Schema {
|
||||
processed.components.schemas[key] = process_schema(schema, name:key)
|
||||
processed.components.schemas[key] = process_schema(schema, name: key)
|
||||
}
|
||||
}
|
||||
|
||||
for path, item in spec.paths {
|
||||
mut processed_item := PathItem{...item}
|
||||
mut processed_item := PathItem{
|
||||
...item
|
||||
}
|
||||
|
||||
processed_item.get = processed.process_operation(item.get, 'get', path)!
|
||||
processed_item.post = processed.process_operation(item.post, 'post', path)!
|
||||
processed_item.put = processed.process_operation(item.put, 'put', path)!
|
||||
processed_item.delete = processed.process_operation(item.delete, 'delete', path)!
|
||||
processed_item.patch = processed.process_operation(item.patch, 'patch', path)!
|
||||
processed_item.options = processed.process_operation(item.options, 'options', path)!
|
||||
processed_item.options = processed.process_operation(item.options, 'options',
|
||||
path)!
|
||||
processed_item.head = processed.process_operation(item.head, 'head', path)!
|
||||
processed_item.trace = processed.process_operation(item.trace, 'trace', path)!
|
||||
|
||||
@@ -83,8 +100,9 @@ pub fn process(spec OpenAPI) !OpenAPI {
|
||||
}
|
||||
|
||||
fn (spec OpenAPI) process_operation(op Operation, method string, path string) !Operation {
|
||||
mut processed := Operation{...op
|
||||
responses: op.responses.clone()
|
||||
mut processed := Operation{
|
||||
...op
|
||||
responses: op.responses.clone()
|
||||
}
|
||||
|
||||
if op.is_empty() {
|
||||
@@ -97,38 +115,37 @@ fn (spec OpenAPI) process_operation(op Operation, method string, path string) !O
|
||||
if op.request_body is RequestBody {
|
||||
if content := op.request_body.content['application/json'] {
|
||||
if content.schema is Reference {
|
||||
mut req_body_ := RequestBody{...op.request_body
|
||||
mut req_body_ := RequestBody{
|
||||
...op.request_body
|
||||
content: op.request_body.content.clone()
|
||||
}
|
||||
req_body_.content['application/json'].schema = SchemaRef(
|
||||
process_schema(spec.dereference_schema(content.schema)!, name: content.schema.ref.all_after_last('/'))
|
||||
)
|
||||
req_body_.content['application/json'].schema = SchemaRef(process_schema(spec.dereference_schema(content.schema)!,
|
||||
name: content.schema.ref.all_after_last('/')
|
||||
))
|
||||
processed.request_body = RequestBodyRef(req_body_)
|
||||
} else if content.schema is Schema {
|
||||
mut req_body_ := RequestBody{...op.request_body
|
||||
mut req_body_ := RequestBody{
|
||||
...op.request_body
|
||||
content: op.request_body.content.clone()
|
||||
}
|
||||
req_body_.content['application/json'].schema = SchemaRef(
|
||||
process_schema(content.schema)
|
||||
)
|
||||
req_body_.content['application/json'].schema = SchemaRef(process_schema(content.schema))
|
||||
processed.request_body = RequestBodyRef(req_body_)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if response_spec := processed.responses['200']{
|
||||
mut processed_rs := ResponseSpec{...response_spec
|
||||
if response_spec := processed.responses['200'] {
|
||||
mut processed_rs := ResponseSpec{
|
||||
...response_spec
|
||||
content: response_spec.content.clone()
|
||||
}
|
||||
if media_type := processed_rs.content['application/json'] {
|
||||
if media_type.schema is Reference {
|
||||
processed_rs.content['application/json'].schema = SchemaRef(
|
||||
process_schema(spec.dereference_schema(media_type.schema)!, name: media_type.schema.ref.all_after_last('/'))
|
||||
)
|
||||
processed_rs.content['application/json'].schema = SchemaRef(process_schema(spec.dereference_schema(media_type.schema)!,
|
||||
name: media_type.schema.ref.all_after_last('/')
|
||||
))
|
||||
} else if media_type.schema is Schema {
|
||||
processed_rs.content['application/json'].schema = SchemaRef(
|
||||
process_schema(media_type.schema)
|
||||
)
|
||||
processed_rs.content['application/json'].schema = SchemaRef(process_schema(media_type.schema))
|
||||
}
|
||||
}
|
||||
processed.responses['200'] = processed_rs
|
||||
@@ -139,13 +156,9 @@ fn (spec OpenAPI) process_operation(op Operation, method string, path string) !O
|
||||
|
||||
// Helper function to generate a unique operationId
|
||||
fn generate_operation_id(method string, path string) string {
|
||||
// Convert HTTP method and path into a camelCase string
|
||||
method_part := method.to_lower()
|
||||
path_part := texttools.snake_case(path.all_before('{')
|
||||
.replace('/', '_') // Replace slashes with underscores
|
||||
.replace('{', '') // Remove braces around path parameters
|
||||
.replace('}', '') // Remove braces around path parameters
|
||||
)
|
||||
|
||||
return '${method_part}_${path_part}'
|
||||
}
|
||||
// Convert HTTP method and path into a camelCase string
|
||||
method_part := method.to_lower()
|
||||
d := path.all_before('{').replace('/', '_').replace('{', '').replace('}', '')
|
||||
path_part := texttools.snake_case(d)
|
||||
return '${method_part}_${path_part}'
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
module openapi
|
||||
|
||||
import net.http {CommonHeader}
|
||||
import x.json2 {Any}
|
||||
import net.http
|
||||
import x.json2 { Any }
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
pub struct Request {
|
||||
pub:
|
||||
path string // The requested path
|
||||
method string // HTTP method (e.g., GET, POST)
|
||||
key string
|
||||
body string // Request body
|
||||
operation Operation
|
||||
arguments map[string]Any
|
||||
path string // The requested path
|
||||
method string // HTTP method (e.g., GET, POST)
|
||||
key string
|
||||
body string // Request body
|
||||
operation Operation
|
||||
arguments map[string]Any
|
||||
parameters map[string]string
|
||||
header http.Header @[omitempty; str: skip; json: '-']// Request headers
|
||||
header http.Header @[json: '-'; omitempty; str: skip] // Request headers
|
||||
}
|
||||
|
||||
pub struct Response {
|
||||
pub mut:
|
||||
status http.Status // HTTP status
|
||||
body string // Response body
|
||||
header http.Header @[omitempty; str: skip; json:'-']// Response headers
|
||||
status http.Status // HTTP status
|
||||
body string // Response body
|
||||
header http.Header @[json: '-'; omitempty; str: skip] // Response headers
|
||||
}
|
||||
|
||||
pub struct Handler {
|
||||
pub:
|
||||
specification OpenAPI @[required] // The OpenRPC specification
|
||||
pub mut:
|
||||
handler IHandler
|
||||
handler IHandler
|
||||
}
|
||||
|
||||
pub interface IHandler {
|
||||
@@ -38,7 +38,7 @@ mut:
|
||||
@[params]
|
||||
pub struct HandleParams {
|
||||
timeout int = 60 // Timeout in seconds
|
||||
retry int // Number of retries
|
||||
retry int // Number of retries
|
||||
}
|
||||
|
||||
// Handle a JSON-RPC request and return a response
|
||||
@@ -63,4 +63,4 @@ pub fn (mut h Handler) handle(req Request, params HandleParams) !Response {
|
||||
|
||||
// Forward the request to the custom handler
|
||||
return h.handler.handle(req)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,21 @@ module openapi
|
||||
|
||||
import maps
|
||||
import net.http
|
||||
import x.json2 as json {Any}
|
||||
import freeflowuniverse.herolib.schemas.jsonschema {Schema, Reference, SchemaRef}
|
||||
import x.json2 as json { Any }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Reference, Schema, SchemaRef }
|
||||
|
||||
// todo: report bug: when comps is optional, doesnt work
|
||||
pub struct OpenAPI {
|
||||
pub mut:
|
||||
openapi string @[required] // This string MUST be the version number of the OpenAPI Specification that the OpenAPI document uses. The openapi field SHOULD be used by tooling to interpret the OpenAPI document. This is not related to the API info.version string.
|
||||
info Info @[required] // Provides metadata about the API. The metadata MAY be used by tooling as required.
|
||||
json_schema_dialect string // The default value for the $schema keyword within Schema Objects contained within this OAS document. This MUST be in the form of a URI.
|
||||
servers []ServerSpec // An array of ServerSpec Objects, which provide connectivity information to a target server. If the servers property is not provided, or is an empty array, the default value would be a ServerSpec Object with a url value of /.
|
||||
paths map[string]PathItem // The available paths and operations for the API.
|
||||
webhooks map[string]PathRef // The incoming webhooks that MAY be received as part of this API and that the API consumer MAY choose to implement. Closely related to the callbacks feature, this section describes requests initiated other than by an API call, for example by an out of band registration. The key name is a unique string to refer to each webhook, while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider and the expected responses. An example is available.
|
||||
components Components // An element to hold various schemas for the document.
|
||||
openapi string @[required] // This string MUST be the version number of the OpenAPI Specification that the OpenAPI document uses. The openapi field SHOULD be used by tooling to interpret the OpenAPI document. This is not related to the API info.version string.
|
||||
info Info @[required] // Provides metadata about the API. The metadata MAY be used by tooling as required.
|
||||
json_schema_dialect string // The default value for the $schema keyword within Schema Objects contained within this OAS document. This MUST be in the form of a URI.
|
||||
servers []ServerSpec // An array of ServerSpec Objects, which provide connectivity information to a target server. If the servers property is not provided, or is an empty array, the default value would be a ServerSpec Object with a url value of /.
|
||||
paths map[string]PathItem // The available paths and operations for the API.
|
||||
webhooks map[string]PathRef // The incoming webhooks that MAY be received as part of this API and that the API consumer MAY choose to implement. Closely related to the callbacks feature, this section describes requests initiated other than by an API call, for example by an out of band registration. The key name is a unique string to refer to each webhook, while the (optionally referenced) Path Item Object describes a request that may be initiated by the API provider and the expected responses. An example is available.
|
||||
components Components // An element to hold various schemas for the document.
|
||||
security []SecurityRequirement // A declaration of which security mechanisms can be used across the API. The list of values includes alternative security requirement objects that can be used. Only one of the security requirement objects need to be satisfied to authorize a request. Individual operations can override this definition. To make security optional, an empty security requirement ({}) can be included in the array.
|
||||
tags []Tag // A list of tags used by the document with additional metadata. The order of the tags can be used to reflect on their order by the parsing tools. Not all tags that are used by the Operation Object must be declared. The tags that are not declared MAY be organized randomly or based on the tools’ logic. Each tag name in the list MUST be unique.
|
||||
tags []Tag // A list of tags used by the document with additional metadata. The order of the tags can be used to reflect on their order by the parsing tools. Not all tags that are used by the Operation Object must be declared. The tags that are not declared MAY be organized randomly or based on the tools’ logic. Each tag name in the list MUST be unique.
|
||||
external_docs ExternalDocumentation // Additional external documentation.
|
||||
}
|
||||
|
||||
@@ -45,13 +45,13 @@ pub fn (spec OpenAPI) plain() string {
|
||||
// The object provides metadata about the API. The metadata MAY be used by the clients if needed, and MAY be presented in editing or documentation generation tools for convenience.
|
||||
pub struct Info {
|
||||
pub mut:
|
||||
title string @[required] // The title of the API
|
||||
title string @[required] // The title of the API
|
||||
summary string // A short summary of the API.
|
||||
description string // A description of the API. CommonMark syntax MAY be used for rich text representation.
|
||||
terms_of_service string // A URL to the Terms of Service for the API. This MUST be in the form of a URL.
|
||||
contact Contact // The contact information for the exposed API.
|
||||
license License // The license information for the exposed API.
|
||||
version string @[required] // The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API implementation version).
|
||||
version string @[required] // The version of the OpenAPI document (which is distinct from the OpenAPI Specification version or the API implementation version).
|
||||
}
|
||||
|
||||
// ```{
|
||||
@@ -79,24 +79,23 @@ pub:
|
||||
url string // A URL to the license used for the API. This MUST be in the form of a URL. The url field is mutually exclusive of the identifier field.
|
||||
}
|
||||
|
||||
|
||||
// ```{
|
||||
// "url": "https://development.gigantic-server.com/v1",
|
||||
// "description": "Development server"
|
||||
// }```
|
||||
pub struct ServerSpec {
|
||||
pub:
|
||||
url string @[required] // A URL to the target host. This URL supports ServerSpec Variables and MAY be relative, to indicate that the host location is relative to the location where the OpenAPI document is being served. Variable substitutions will be made when a variable is named in {brackets}.
|
||||
url string @[required] // A URL to the target host. This URL supports ServerSpec Variables and MAY be relative, to indicate that the host location is relative to the location where the OpenAPI document is being served. Variable substitutions will be made when a variable is named in {brackets}.
|
||||
description string // An optional string describing the host designated by the URL. CommonMark syntax MAY be used for rich text representation.
|
||||
variables map[string]ServerVariable @[omitempty]// A map between a variable name and its value. The value is used for substitution in the server’s URL template.
|
||||
variables map[string]ServerVariable @[omitempty] // A map between a variable name and its value. The value is used for substitution in the server’s URL template.
|
||||
}
|
||||
|
||||
// An object representing a ServerSpec Variable for server URL template substitution.
|
||||
pub struct ServerVariable {
|
||||
pub:
|
||||
enum_ []string @[json: 'enum'; omitempty] // An enumeration of string values to be used if the substitution options are from a limited set.
|
||||
default_ string @[json: 'default'; required; omitempty] // The default value to use for substitution, which SHALL be sent if an alternate value is not supplied. Note this behavior is different than the Schema Object’s treatment of default values, because in those cases parameter values are optional.
|
||||
description string @[omitempty] // An optional description for the server variable. GitHub Flavored Markdown syntax MAY be used for rich text representation.
|
||||
default_ string @[json: 'default'; omitempty; required] // The default value to use for substitution, which SHALL be sent if an alternate value is not supplied. Note this behavior is different than the Schema Object’s treatment of default values, because in those cases parameter values are optional.
|
||||
description string @[omitempty] // An optional description for the server variable. GitHub Flavored Markdown syntax MAY be used for rich text representation.
|
||||
}
|
||||
|
||||
pub struct Path {}
|
||||
@@ -111,16 +110,16 @@ pub type PathRef = Path | Reference
|
||||
|
||||
pub struct Components {
|
||||
pub mut:
|
||||
schemas map[string]SchemaRef @[omitempty] // An object to hold reusable Schema Objects.
|
||||
responses map[string]ResponseRef @[omitempty] // An object to hold reusable ResponseSpec Objects.
|
||||
parameters map[string]ParameterRef @[omitempty] // An object to hold reusable Parameter Objects.
|
||||
examples map[string]ExampleRef @[omitempty] // An object to hold reusable Example Objects.
|
||||
request_bodies map[string]RequestBodyRef @[omitempty] // An object to hold reusable Request Body Objects.
|
||||
headers map[string]HeaderRef @[omitempty] // An object to hold reusable Header Objects.
|
||||
security_schemes map[string]SecuritySchemeRef @[omitempty]// An object to hold reusable Security Scheme Objects.
|
||||
links map[string]LinkRef @[omitempty] // An object to hold reusable Link Objects.
|
||||
callbacks map[string]CallbackRef @[omitempty]// An object to hold reusable Callback Objects.
|
||||
path_items map[string]PathItemRef @[omitempty]// An object to hold reusable Path Item Object.
|
||||
schemas map[string]SchemaRef @[omitempty] // An object to hold reusable Schema Objects.
|
||||
responses map[string]ResponseRef @[omitempty] // An object to hold reusable ResponseSpec Objects.
|
||||
parameters map[string]ParameterRef @[omitempty] // An object to hold reusable Parameter Objects.
|
||||
examples map[string]ExampleRef @[omitempty] // An object to hold reusable Example Objects.
|
||||
request_bodies map[string]RequestBodyRef @[omitempty] // An object to hold reusable Request Body Objects.
|
||||
headers map[string]HeaderRef @[omitempty] // An object to hold reusable Header Objects.
|
||||
security_schemes map[string]SecuritySchemeRef @[omitempty] // An object to hold reusable Security Scheme Objects.
|
||||
links map[string]LinkRef @[omitempty] // An object to hold reusable Link Objects.
|
||||
callbacks map[string]CallbackRef @[omitempty] // An object to hold reusable Callback Objects.
|
||||
path_items map[string]PathItemRef @[omitempty] // An object to hold reusable Path Item Object.
|
||||
}
|
||||
|
||||
pub fn (s OpenAPI) dereference_schema(ref Reference) !Schema {
|
||||
@@ -152,108 +151,109 @@ pub type HeaderRef = Header | Reference
|
||||
pub type LinkRef = Link | Reference
|
||||
pub type CallbackRef = Callback | Reference
|
||||
pub type PathItemRef = PathItem | Reference
|
||||
|
||||
// type RequestRef = Reference | Request
|
||||
|
||||
pub struct OperationInfo {
|
||||
pub:
|
||||
operation Operation
|
||||
path string
|
||||
method http.Method
|
||||
path string
|
||||
method http.Method
|
||||
}
|
||||
|
||||
pub fn (s OpenAPI) get_operations() []OperationInfo {
|
||||
return maps.flat_map[string, PathItem, OperationInfo](s.paths, fn(path string, item PathItem) []OperationInfo {
|
||||
return item.get_operations().map(OperationInfo{...it, path: path})
|
||||
return maps.flat_map[string, PathItem, OperationInfo](s.paths, fn (path string, item PathItem) []OperationInfo {
|
||||
return item.get_operations().map(OperationInfo{ ...it, path: path })
|
||||
})
|
||||
}
|
||||
|
||||
// get all operations for path as list of tuple [](http.Method, openapi.Operation)
|
||||
pub fn (item PathItem) get_operations() []OperationInfo {
|
||||
mut ops := []OperationInfo
|
||||
mut ops := []OperationInfo{}
|
||||
|
||||
if !item.get.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .get
|
||||
operation: item.get
|
||||
if !item.get.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .get
|
||||
operation: item.get
|
||||
}
|
||||
}
|
||||
}
|
||||
if !item.post.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .post
|
||||
operation: item.post
|
||||
if !item.post.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .post
|
||||
operation: item.post
|
||||
}
|
||||
}
|
||||
}
|
||||
if !item.put.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .put
|
||||
operation: item.put
|
||||
if !item.put.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .put
|
||||
operation: item.put
|
||||
}
|
||||
}
|
||||
}
|
||||
if !item.delete.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .delete
|
||||
operation: item.delete
|
||||
if !item.delete.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .delete
|
||||
operation: item.delete
|
||||
}
|
||||
}
|
||||
}
|
||||
if !item.patch.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .patch
|
||||
operation: item.patch
|
||||
if !item.patch.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .patch
|
||||
operation: item.patch
|
||||
}
|
||||
}
|
||||
}
|
||||
if !item.head.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .head
|
||||
operation: item.head
|
||||
if !item.head.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .head
|
||||
operation: item.head
|
||||
}
|
||||
}
|
||||
}
|
||||
if !item.options.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .options
|
||||
operation: item.options
|
||||
if !item.options.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .options
|
||||
operation: item.options
|
||||
}
|
||||
}
|
||||
}
|
||||
if !item.trace.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .trace
|
||||
operation: item.trace
|
||||
if !item.trace.is_empty() {
|
||||
ops << OperationInfo{
|
||||
method: .trace
|
||||
operation: item.trace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ops
|
||||
return ops
|
||||
}
|
||||
|
||||
pub struct PathItem {
|
||||
pub mut:
|
||||
ref string @[omitempty] // Allows for a referenced definition of this path item. The referenced structure MUST be in the form of a Path Item Object. In case a Path Item Object field appears both in the defined object and the referenced object, the behavior is undefined. See the rules for resolving Relative References.
|
||||
summary string @[omitempty] // An optional, string summary, intended to apply to all operations in this path.
|
||||
description string @[omitempty] // An optional, string description, intended to apply to all operations in this path. CommonMark syntax MAY be used for rich text representation.
|
||||
get Operation @[omitempty] // A definition of a GET operation on this path.
|
||||
put Operation @[omitempty] // A definition of a PUT operation on this path.
|
||||
post Operation @[omitempty] // A definition of a POST operation on this path.
|
||||
delete Operation @[omitempty] // A definition of a DELETE operation on this path.
|
||||
options Operation @[omitempty] // A definition of a OPTIONS operation on this path.
|
||||
head Operation @[omitempty] // A definition of a HEAD operation on this path.
|
||||
patch Operation @[omitempty] // A definition of a PATCH operation on this path.
|
||||
trace Operation @[omitempty] // A definition of a TRACE operation on this path.
|
||||
servers []ServerSpec @[omitempty] // An alternative server array to service all operations in this path.
|
||||
parameters []Parameter @[omitempty]// A list of parameters that are applicable for all the operations described under this path. These parameters can be overridden at the operation level, but cannot be removed there. The list MUST NOT include duplicated parameters. A unique parameter is defined by a combination of a name and location. The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object’s components/parameters.
|
||||
ref string @[omitempty] // Allows for a referenced definition of this path item. The referenced structure MUST be in the form of a Path Item Object. In case a Path Item Object field appears both in the defined object and the referenced object, the behavior is undefined. See the rules for resolving Relative References.
|
||||
summary string @[omitempty] // An optional, string summary, intended to apply to all operations in this path.
|
||||
description string @[omitempty] // An optional, string description, intended to apply to all operations in this path. CommonMark syntax MAY be used for rich text representation.
|
||||
get Operation @[omitempty] // A definition of a GET operation on this path.
|
||||
put Operation @[omitempty] // A definition of a PUT operation on this path.
|
||||
post Operation @[omitempty] // A definition of a POST operation on this path.
|
||||
delete Operation @[omitempty] // A definition of a DELETE operation on this path.
|
||||
options Operation @[omitempty] // A definition of a OPTIONS operation on this path.
|
||||
head Operation @[omitempty] // A definition of a HEAD operation on this path.
|
||||
patch Operation @[omitempty] // A definition of a PATCH operation on this path.
|
||||
trace Operation @[omitempty] // A definition of a TRACE operation on this path.
|
||||
servers []ServerSpec @[omitempty] // An alternative server array to service all operations in this path.
|
||||
parameters []Parameter @[omitempty] // A list of parameters that are applicable for all the operations described under this path. These parameters can be overridden at the operation level, but cannot be removed there. The list MUST NOT include duplicated parameters. A unique parameter is defined by a combination of a name and location. The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object’s components/parameters.
|
||||
}
|
||||
|
||||
pub struct Operation {
|
||||
pub mut:
|
||||
tags []string @[omitempty]// A list of tags for API documentation control. Tags can be used for logical grouping of operations by resources or any other qualifier.
|
||||
summary string @[omitempty]// A short summary of what the operation does.
|
||||
description string @[omitempty] // A verbose explanation of the operation behavior. CommonMark syntax MAY be used for rich text representation.
|
||||
external_docs ExternalDocumentation @[json: 'externalDocs'; omitempty] // Additional external documentation for this operation.
|
||||
operation_id string @[json: 'operationId'; omitempty] // Unique string used to identify the operation. The id MUST be unique among all operations described in the API. The operationId value is case-sensitive. Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is RECOMMENDED to follow common programming naming conventions.
|
||||
parameters []Parameter @[omitempty]// A list of parameters that are applicable for this operation. If a parameter is already defined at the Path Item, the new definition will override it but can never remove it. The list MUST NOT include duplicated parameters. A unique parameter is defined by a combination of a name and location. The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object’s components/parameters.
|
||||
request_body RequestBodyRef @[json: 'requestBody'; omitempty] // The request body applicable for this operation. The requestBody is fully supported in HTTP methods where the HTTP 1.1 specification [RFC7231] has explicitly defined semantics for request bodies. In other cases where the HTTP spec is vague (such as GET, HEAD and DELETE), requestBody is permitted but does not have well-defined semantics and SHOULD be avoided if possible.
|
||||
responses map[string]ResponseSpec @[omitempty] // The list of possible responses as they are returned from executing this operation.
|
||||
callbacks map[string]CallbackRef @[omitempty] // A map of possible out-of band callbacks related to the parent operation. The key is a unique identifier for the Callback Object. Each value in the map is a Callback Object that describes a request that may be initiated by the API provider and the expected responses.
|
||||
deprecated bool @[omitempty]// Declares this operation to be deprecated. Consumers SHOULD refrain from usage of the declared operation. Default value is false.
|
||||
security []SecurityRequirement @[omitempty] // A declaration of which security mechanisms can be used for this operation. The list of values includes alternative security requirement objects that can be used. Only one of the security requirement objects need to be satisfied to authorize a request. To make security optional, an empty security requirement ({}) can be included in the array. This definition overrides any declared top-level security. To remove a top-level security declaration, an empty array can be used.
|
||||
servers []ServerSpec @[omitempty]// An alternative server array to service this operation. If an alternative server object is specified at the Path Item Object or Root level, it will be overridden by this value.
|
||||
tags []string @[omitempty] // A list of tags for API documentation control. Tags can be used for logical grouping of operations by resources or any other qualifier.
|
||||
summary string @[omitempty] // A short summary of what the operation does.
|
||||
description string @[omitempty] // A verbose explanation of the operation behavior. CommonMark syntax MAY be used for rich text representation.
|
||||
external_docs ExternalDocumentation @[json: 'externalDocs'; omitempty] // Additional external documentation for this operation.
|
||||
operation_id string @[json: 'operationId'; omitempty] // Unique string used to identify the operation. The id MUST be unique among all operations described in the API. The operationId value is case-sensitive. Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is RECOMMENDED to follow common programming naming conventions.
|
||||
parameters []Parameter @[omitempty] // A list of parameters that are applicable for this operation. If a parameter is already defined at the Path Item, the new definition will override it but can never remove it. The list MUST NOT include duplicated parameters. A unique parameter is defined by a combination of a name and location. The list can use the Reference Object to link to parameters that are defined at the OpenAPI Object’s components/parameters.
|
||||
request_body RequestBodyRef @[json: 'requestBody'; omitempty] // The request body applicable for this operation. The requestBody is fully supported in HTTP methods where the HTTP 1.1 specification [RFC7231] has explicitly defined semantics for request bodies. In other cases where the HTTP spec is vague (such as GET, HEAD and DELETE), requestBody is permitted but does not have well-defined semantics and SHOULD be avoided if possible.
|
||||
responses map[string]ResponseSpec @[omitempty] // The list of possible responses as they are returned from executing this operation.
|
||||
callbacks map[string]CallbackRef @[omitempty] // A map of possible out-of band callbacks related to the parent operation. The key is a unique identifier for the Callback Object. Each value in the map is a Callback Object that describes a request that may be initiated by the API provider and the expected responses.
|
||||
deprecated bool @[omitempty] // Declares this operation to be deprecated. Consumers SHOULD refrain from usage of the declared operation. Default value is false.
|
||||
security []SecurityRequirement @[omitempty] // A declaration of which security mechanisms can be used for this operation. The list of values includes alternative security requirement objects that can be used. Only one of the security requirement objects need to be satisfied to authorize a request. To make security optional, an empty security requirement ({}) can be included in the array. This definition overrides any declared top-level security. To remove a top-level security declaration, an empty array can be used.
|
||||
servers []ServerSpec @[omitempty] // An alternative server array to service this operation. If an alternative server object is specified at the Path Item Object or Root level, it will be overridden by this value.
|
||||
}
|
||||
|
||||
fn (o Operation) is_empty() bool {
|
||||
@@ -276,7 +276,7 @@ pub fn (o Operation) payload_schema() ?Schema {
|
||||
|
||||
// shorthand to get the schema of a response
|
||||
pub fn (o Operation) response_schema() ?Schema {
|
||||
if response_spec := o.responses['200']{
|
||||
if response_spec := o.responses['200'] {
|
||||
if payload_media_type := response_spec.content['application/json'] {
|
||||
if payload_media_type.schema is Schema {
|
||||
return payload_media_type.schema
|
||||
@@ -292,8 +292,10 @@ pub fn (o Operation) response_schema() ?Schema {
|
||||
pub fn (r map[string]ResponseSpec) errors() map[string]ResponseSpec {
|
||||
return maps.filter(r, fn (k string, v ResponseSpec) bool {
|
||||
return if k.is_int() {
|
||||
k.int() >= 400 && k.int()< 600
|
||||
} else { false }
|
||||
k.int() >= 400 && k.int() < 600
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -320,37 +322,37 @@ pub:
|
||||
|
||||
pub struct ResponseSpec {
|
||||
pub mut:
|
||||
description string @[required] // A description of the response. CommonMark syntax MAY be used for rich text representation.
|
||||
headers map[string]HeaderRef @[omitempty]// Maps a header name to its definition. [RFC7230] states header names are case insensitive. If a response header is defined with the name "Content-Type", it SHALL be ignored.
|
||||
content map[string]MediaType @[omitempty]// A map containing descriptions of potential response payloads. The key is a media type or media type range and the value describes it. For responses that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/*
|
||||
links map[string]LinkRef @[omitempty]// A map of operations links that can be followed from the response. The key of the map is a short name for the link, following the naming constraints of the names for Component Objects.
|
||||
description string @[required] // A description of the response. CommonMark syntax MAY be used for rich text representation.
|
||||
headers map[string]HeaderRef @[omitempty] // Maps a header name to its definition. [RFC7230] states header names are case insensitive. If a response header is defined with the name "Content-Type", it SHALL be ignored.
|
||||
content map[string]MediaType @[omitempty] // A map containing descriptions of potential response payloads. The key is a media type or media type range and the value describes it. For responses that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/*
|
||||
links map[string]LinkRef @[omitempty] // A map of operations links that can be followed from the response. The key of the map is a short name for the link, following the naming constraints of the names for Component Objects.
|
||||
}
|
||||
|
||||
// TODO: media type example any field
|
||||
pub struct MediaType {
|
||||
pub mut:
|
||||
schema SchemaRef @[omitempty] // The schema defining the content of the request, response, or parameter.
|
||||
example Any @[json: '-'; omitempty]// Example of the media type. The example object SHOULD be in the correct format as specified by the media type. The example field is mutually exclusive of the examples field. Furthermore, if referencing a schema which contains an example, the example value SHALL override the example provided by the schema.
|
||||
examples map[string]ExampleRef @[omitempty]// Examples of the media type. Each example object SHOULD match the media type and specified schema if present. The examples field is mutually exclusive of the example field. Furthermore, if referencing a schema which contains an example, the examples value SHALL override the example provided by the schema.
|
||||
encoding map[string]Encoding @[omitempty] // A map between a property name and its encoding information. The key, being the property name, MUST exist in the schema as a property. The encoding object SHALL only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded.
|
||||
schema SchemaRef @[omitempty] // The schema defining the content of the request, response, or parameter.
|
||||
example Any @[json: '-'; omitempty] // Example of the media type. The example object SHOULD be in the correct format as specified by the media type. The example field is mutually exclusive of the examples field. Furthermore, if referencing a schema which contains an example, the example value SHALL override the example provided by the schema.
|
||||
examples map[string]ExampleRef @[omitempty] // Examples of the media type. Each example object SHOULD match the media type and specified schema if present. The examples field is mutually exclusive of the example field. Furthermore, if referencing a schema which contains an example, the examples value SHALL override the example provided by the schema.
|
||||
encoding map[string]Encoding @[omitempty] // A map between a property name and its encoding information. The key, being the property name, MUST exist in the schema as a property. The encoding object SHALL only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded.
|
||||
}
|
||||
|
||||
pub struct Encoding {
|
||||
pub:
|
||||
content_type string @[json: 'contentType'] // The Content-Type for encoding a specific property. Default value depends on the property type: for object - application/json; for array – the default is defined based on the inner type; for all other cases the default is application/octet-stream. The value can be a specific media type (e.g. application/json), a wildcard media type (e.g. image/*), or a comma-separated list of the two types.
|
||||
content_type string @[json: 'contentType'] // The Content-Type for encoding a specific property. Default value depends on the property type: for object - application/json; for array – the default is defined based on the inner type; for all other cases the default is application/octet-stream. The value can be a specific media type (e.g. application/json), a wildcard media type (e.g. image/*), or a comma-separated list of the two types.
|
||||
headers map[string]HeaderRef // A map allowing additional information to be provided as headers, for example Content-Disposition. Content-Type is described separately and SHALL be ignored in this section. This property SHALL be ignored if the request body media type is not a multipart.
|
||||
style string // Describes how a specific property value will be serialized depending on its type. See Parameter Object for details on the style property. The behavior follows the same values as query parameters, including default values. This property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded or multipart/form-data. If a value is explicitly defined, then the value of contentType (implicit or explicit) SHALL be ignored.
|
||||
explode bool // When this is true, property values of type array or object generate separate parameters for each value of the array, or key-value-pair of the map. For other types of properties this property has no effect. When style is form, the default value is true. For all other styles, the default value is false. This property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded or multipart/form-data. If a value is explicitly defined, then the value of contentType (implicit or explicit) SHALL be ignored.
|
||||
allow_reserved bool // Determines whether the parameter value SHOULD allow reserved characters, as defined by [RFC3986] :/?#[]@!$&'()*+,;= to be included without percent-encoding. The default value is false. This property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded or multipart/form-data. If a value is explicitly defined, then the value of contentType (implicit or explicit) SHALL be ignored.
|
||||
style string // Describes how a specific property value will be serialized depending on its type. See Parameter Object for details on the style property. The behavior follows the same values as query parameters, including default values. This property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded or multipart/form-data. If a value is explicitly defined, then the value of contentType (implicit or explicit) SHALL be ignored.
|
||||
explode bool // When this is true, property values of type array or object generate separate parameters for each value of the array, or key-value-pair of the map. For other types of properties this property has no effect. When style is form, the default value is true. For all other styles, the default value is false. This property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded or multipart/form-data. If a value is explicitly defined, then the value of contentType (implicit or explicit) SHALL be ignored.
|
||||
allow_reserved bool // Determines whether the parameter value SHOULD allow reserved characters, as defined by [RFC3986] :/?#[]@!$&'()*+,;= to be included without percent-encoding. The default value is false. This property SHALL be ignored if the request body media type is not application/x-www-form-urlencoded or multipart/form-data. If a value is explicitly defined, then the value of contentType (implicit or explicit) SHALL be ignored.
|
||||
}
|
||||
|
||||
pub struct Parameter {
|
||||
pub mut:
|
||||
name string @[required] // The name of the parameter. Parameter names are case sensitive.
|
||||
in_ string @[json: 'in'; required] // The location of the parameter. Possible values are "query", "header", "path" or "cookie".
|
||||
description string @[omitempty]// A brief description of the parameter. This could contain examples of use. CommonMark syntax MAY be used for rich text representation.
|
||||
required bool @[omitempty]// Determines whether this parameter is mandatory. If the parameter location is "path", this property is REQUIRED and its value MUST be true. Otherwise, the property MAY be included and its default value is false.
|
||||
deprecated bool @[omitempty]// Specifies that a parameter is deprecated and SHOULD be transitioned out of usage. Default value is false.
|
||||
name string @[required] // The name of the parameter. Parameter names are case sensitive.
|
||||
in_ string @[json: 'in'; required] // The location of the parameter. Possible values are "query", "header", "path" or "cookie".
|
||||
description string @[omitempty] // A brief description of the parameter. This could contain examples of use. CommonMark syntax MAY be used for rich text representation.
|
||||
required bool @[omitempty] // Determines whether this parameter is mandatory. If the parameter location is "path", this property is REQUIRED and its value MUST be true. Otherwise, the property MAY be included and its default value is false.
|
||||
deprecated bool @[omitempty] // Specifies that a parameter is deprecated and SHOULD be transitioned out of usage. Default value is false.
|
||||
allow_empty_value bool @[json: 'allowEmptyValue'; omitempty] // Sets the ability to pass empty-valued parameters. This is valid only for query parameters and allows sending a parameter with an empty value. Default value is false. If style is used, and if behavior is n/a (cannot be serialized), the value of allowEmptyValue SHALL be ignored. Use of this property is NOT RECOMMENDED, as it is likely to be removed in a later revision.
|
||||
schema SchemaRef // The schema defining the type used for the parameter.
|
||||
}
|
||||
@@ -363,9 +365,9 @@ pub struct SecurityScheme {}
|
||||
|
||||
pub struct RequestBody {
|
||||
pub mut:
|
||||
description string // A brief description of the request body. This could contain examples of use. CommonMark syntax MAY be used for rich text representation.
|
||||
content map[string]MediaType // The content of the request body. The key is a media type (e.g., `application/json`) and the value describes it.
|
||||
required bool // Determines if the request body is required in the request. Defaults to false.
|
||||
description string // A brief description of the request body. This could contain examples of use. CommonMark syntax MAY be used for rich text representation.
|
||||
content map[string]MediaType // The content of the request body. The key is a media type (e.g., `application/json`) and the value describes it.
|
||||
required bool // Determines if the request body is required in the request. Defaults to false.
|
||||
}
|
||||
|
||||
pub struct SecurityRequirement {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module codegen
|
||||
|
||||
import freeflowuniverse.herolib.core.code { VFile, File, Function, Struct , Module}
|
||||
import freeflowuniverse.herolib.schemas.openrpc {OpenRPC}
|
||||
import freeflowuniverse.herolib.core.code { Function, Module, Struct }
|
||||
import freeflowuniverse.herolib.schemas.openrpc { OpenRPC }
|
||||
|
||||
// pub struct OpenRPCCode {
|
||||
// pub mut:
|
||||
@@ -14,7 +14,6 @@ import freeflowuniverse.herolib.schemas.openrpc {OpenRPC}
|
||||
// server_test VFile
|
||||
// }
|
||||
|
||||
|
||||
pub fn generate_module(o OpenRPC, receiver Struct, methods_map map[string]Function, objects_map map[string]Struct) !Module {
|
||||
// openrpc_json := o.encode()!
|
||||
// openrpc_file := File{
|
||||
@@ -33,13 +32,13 @@ pub fn generate_module(o OpenRPC, receiver Struct, methods_map map[string]Functi
|
||||
interface_test_file := generate_interface_test_file(o)!
|
||||
|
||||
return Module{
|
||||
files: [
|
||||
client_file
|
||||
client_test_file
|
||||
handler_file
|
||||
handler_test_file
|
||||
interface_file
|
||||
interface_test_file
|
||||
]
|
||||
files: [
|
||||
client_file,
|
||||
client_test_file,
|
||||
handler_file,
|
||||
handler_test_file,
|
||||
interface_file,
|
||||
interface_test_file,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module codegen
|
||||
|
||||
import freeflowuniverse.herolib.core.code { VFile, CodeItem, CustomCode, Function, Struct, parse_function }
|
||||
import freeflowuniverse.herolib.core.code { CodeItem, CustomCode, Function, Struct, VFile, parse_function }
|
||||
// import freeflowuniverse.herolib.schemas.jsonrpc.codegen {generate_client_struct}
|
||||
import freeflowuniverse.herolib.schemas.openrpc {OpenRPC}
|
||||
import freeflowuniverse.herolib.schemas.openrpc { OpenRPC }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
// generate_structs geenrates struct codes for schemas defined in an openrpc document
|
||||
@@ -21,7 +21,7 @@ pub fn generate_client_file(o OpenRPC, object_map map[string]Struct) !VFile {
|
||||
// code << methods.map(CodeItem(it))
|
||||
mut file := VFile{
|
||||
name: 'client'
|
||||
mod: name
|
||||
mod: name
|
||||
// imports: imports
|
||||
items: items
|
||||
}
|
||||
@@ -54,18 +54,18 @@ pub fn generate_client_test_file(o OpenRPC, methods_map map[string]Function, obj
|
||||
items << func
|
||||
}
|
||||
mut file := VFile{
|
||||
name: 'client_test'
|
||||
mod: name
|
||||
name: 'client_test'
|
||||
mod: name
|
||||
imports: [
|
||||
code.parse_import('freeflowuniverse.herolib.schemas.jsonrpc'),
|
||||
code.parse_import('freeflowuniverse.herolib.schemas.rpcwebsocket'),
|
||||
code.parse_import('log'),
|
||||
]
|
||||
items: items
|
||||
items: items
|
||||
}
|
||||
|
||||
for key, object in object_map {
|
||||
file.add_import(mod: object.mod, types: [object.name])!
|
||||
}
|
||||
return file
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module codegen
|
||||
|
||||
import freeflowuniverse.herolib.core.code { VFile, CodeItem, Param, CustomCode, Function, Result, Struct, parse_import }
|
||||
import freeflowuniverse.herolib.schemas.openrpc {OpenRPC}
|
||||
import freeflowuniverse.herolib.core.code { CodeItem, CustomCode, Function, Param, Struct, VFile, parse_import }
|
||||
import freeflowuniverse.herolib.schemas.openrpc { OpenRPC }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import rand
|
||||
|
||||
@@ -16,8 +16,8 @@ pub fn generate_handler_file(o OpenRPC, receiver Struct, method_map map[string]F
|
||||
]
|
||||
|
||||
mut file := VFile{
|
||||
name: 'handler'
|
||||
mod: name
|
||||
name: 'handler'
|
||||
mod: name
|
||||
imports: imports
|
||||
// TODO
|
||||
// items: jsonrpc.generate_handler(
|
||||
@@ -59,11 +59,11 @@ pub fn generate_handler_test_file(o OpenRPC, receiver Struct, method_map map[str
|
||||
continue
|
||||
}
|
||||
method_handle_test := Function{
|
||||
name: 'test_handle_${method.name}'
|
||||
name: 'test_handle_${method.name}'
|
||||
result: Param{
|
||||
is_result: true
|
||||
}
|
||||
body: "mut handler := ${receiver.name}Handler {${handler_name}.get(name: actor_name)!}
|
||||
body: "mut handler := ${receiver.name}Handler {${handler_name}.get(name: actor_name)!}
|
||||
request := new_jsonrpcrequest[${method.params[0].typ.symbol()}]('${method.name}', ${get_mock_value(method.params[0].typ.symbol())!})
|
||||
response_json := handler.handle(request.to_json())!"
|
||||
}
|
||||
@@ -83,10 +83,10 @@ pub fn generate_handler_test_file(o OpenRPC, receiver Struct, method_map map[str
|
||||
imports := parse_import('freeflowuniverse.herolib.schemas.jsonrpc {new_jsonrpcrequest, jsonrpcresponse_decode, jsonrpcerror_decode}')
|
||||
|
||||
mut file := VFile{
|
||||
name: 'handler_test'
|
||||
mod: name
|
||||
name: 'handler_test'
|
||||
mod: name
|
||||
imports: [imports]
|
||||
items: items
|
||||
items: items
|
||||
}
|
||||
|
||||
for key, object in object_map {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
module codegen
|
||||
|
||||
import freeflowuniverse.herolib.core.code { VFile, CustomCode, parse_function, parse_import }
|
||||
import freeflowuniverse.herolib.core.code { CustomCode, VFile, parse_function, parse_import }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.schemas.openrpc {OpenRPC}
|
||||
import freeflowuniverse.herolib.schemas.openrpc { OpenRPC }
|
||||
|
||||
// pub fn (mut handler AccountantHandler) handle_ws(client &websocket.Client, message string) string {
|
||||
// return handler.handle(message) or { panic(err) }
|
||||
@@ -31,15 +31,16 @@ pub fn generate_interface_file(specification OpenRPC) !VFile {
|
||||
items := handle_ws_fn
|
||||
|
||||
return VFile{
|
||||
mod: name
|
||||
name: 'server'
|
||||
mod: name
|
||||
name: 'server'
|
||||
imports: [
|
||||
parse_import('log'),
|
||||
parse_import('net.websocket'),
|
||||
parse_import('freeflowuniverse.herolib.schemas.rpcwebsocket {RpcWsServer}'),
|
||||
]
|
||||
items: [
|
||||
handle_ws_fn, run_wsserver_fn
|
||||
items: [
|
||||
handle_ws_fn,
|
||||
run_wsserver_fn,
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -60,8 +61,8 @@ pub fn generate_interface_test_file(specification OpenRPC) !VFile {
|
||||
test_fn.body = 'spawn run_wsserver(port)'
|
||||
|
||||
return VFile{
|
||||
mod: name
|
||||
name: 'server_test'
|
||||
mod: name
|
||||
name: 'server_test'
|
||||
items: [
|
||||
CustomCode{'const port = 3000'},
|
||||
test_fn,
|
||||
|
||||
@@ -3,7 +3,7 @@ module codegen
|
||||
import freeflowuniverse.herolib.core.code { CodeItem }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Schema }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen { schema_to_code }
|
||||
import freeflowuniverse.herolib.schemas.openrpc {OpenRPC}
|
||||
import freeflowuniverse.herolib.schemas.openrpc { OpenRPC }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
// generate_structs geenrates struct codes for schemas defined in an openrpc document
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module codegen
|
||||
|
||||
import freeflowuniverse.herolib.core.code { VFile, CodeItem, CustomCode, Function, Struct, parse_function }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen {schemaref_to_type, schema_to_struct}
|
||||
import freeflowuniverse.herolib.schemas.jsonschema {Schema}
|
||||
import freeflowuniverse.herolib.schemas.openrpc {Method, ContentDescriptor}
|
||||
import freeflowuniverse.herolib.core.code { Function, Struct }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema.codegen as jsonschema_codegen { schema_to_struct, schemaref_to_type }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Schema }
|
||||
import freeflowuniverse.herolib.schemas.openrpc { ContentDescriptor, Method }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
// converts OpenRPC Method to Code Function
|
||||
@@ -21,7 +21,7 @@ pub fn method_to_function(method Method) !Function {
|
||||
}
|
||||
|
||||
return Function{
|
||||
name: texttools.snake_case(method.name)
|
||||
name: texttools.snake_case(method.name)
|
||||
params: params
|
||||
result: result
|
||||
}
|
||||
@@ -42,7 +42,7 @@ pub fn content_descriptor_to_struct(cd ContentDescriptor) Struct {
|
||||
pub fn content_descriptor_to_parameter(cd ContentDescriptor) !code.Param {
|
||||
return code.Param{
|
||||
name: cd.name
|
||||
typ: schemaref_to_type(cd.schema)
|
||||
typ: schemaref_to_type(cd.schema)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,48 +5,48 @@ import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
// Main controller for handling RPC requests
|
||||
pub struct HTTPController {
|
||||
Handler // Handles JSON-RPC requests
|
||||
// pub mut:
|
||||
// handler Handler @[required]
|
||||
Handler // Handles JSON-RPC requests
|
||||
// pub mut:
|
||||
// handler Handler @[required]
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
veb.Context
|
||||
}
|
||||
|
||||
// Creates a new HTTPController instance
|
||||
pub fn new_http_controller(c HTTPController) &HTTPController {
|
||||
return &HTTPController{
|
||||
...c,
|
||||
Handler: c.Handler
|
||||
}
|
||||
...c
|
||||
Handler: c.Handler
|
||||
}
|
||||
}
|
||||
|
||||
// Parameters for running the server
|
||||
@[params]
|
||||
pub struct RunParams {
|
||||
pub:
|
||||
port int = 8080 // Default to port 8080
|
||||
port int = 8080 // Default to port 8080
|
||||
}
|
||||
|
||||
// Starts the server
|
||||
pub fn (mut c HTTPController) run(params RunParams) {
|
||||
veb.run[HTTPController, Context](mut c, 8080)
|
||||
veb.run[HTTPController, Context](mut c, 8080)
|
||||
}
|
||||
|
||||
// Handles POST requests at the index endpoint
|
||||
@[post]
|
||||
pub fn (mut c HTTPController) index(mut ctx Context) veb.Result {
|
||||
// Decode JSONRPC Request from POST data
|
||||
request := jsonrpc.decode_request(ctx.req.data) or {
|
||||
return ctx.server_error('Failed to decode JSONRPC Request ${err.msg()}')
|
||||
}
|
||||
// Decode JSONRPC Request from POST data
|
||||
request := jsonrpc.decode_request(ctx.req.data) or {
|
||||
return ctx.server_error('Failed to decode JSONRPC Request ${err.msg()}')
|
||||
}
|
||||
|
||||
// Process the JSONRPC request with the OpenRPC handler
|
||||
response := c.handler.handle(request) or {
|
||||
return ctx.server_error('Handler error: ${err.msg()}')
|
||||
}
|
||||
// Process the JSONRPC request with the OpenRPC handler
|
||||
response := c.handler.handle(request) or {
|
||||
return ctx.server_error('Handler error: ${err.msg()}')
|
||||
}
|
||||
|
||||
// Encode and return the handler's JSONRPC Response
|
||||
return ctx.json(response)
|
||||
}
|
||||
// Encode and return the handler's JSONRPC Response
|
||||
return ctx.json(response)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ module openrpc
|
||||
|
||||
import os
|
||||
import veb
|
||||
import x.json2 {Any}
|
||||
import net.http {CommonHeader}
|
||||
import x.json2
|
||||
import net.http
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
const specification_path = os.join_path(os.dir(@FILE), '/testdata/openrpc.json')
|
||||
@@ -12,29 +12,29 @@ pub struct TestHandler {}
|
||||
|
||||
// handler for test echoes JSONRPC Request as JSONRPC Response
|
||||
pub fn (h TestHandler) handle(request jsonrpc.Request) !jsonrpc.Response {
|
||||
return jsonrpc.Response {
|
||||
jsonrpc: request.jsonrpc
|
||||
id: request.id
|
||||
result: request.params
|
||||
}
|
||||
return jsonrpc.Response{
|
||||
jsonrpc: request.jsonrpc
|
||||
id: request.id
|
||||
result: request.params
|
||||
}
|
||||
}
|
||||
|
||||
fn test_new_server() {
|
||||
new_http_controller(
|
||||
Handler: Handler{
|
||||
specification: new(path: specification_path)!
|
||||
handler:TestHandler{}
|
||||
}
|
||||
)
|
||||
new_http_controller(
|
||||
Handler: Handler{
|
||||
specification: new(path: specification_path)!
|
||||
handler: TestHandler{}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fn test_run_server() {
|
||||
specification := new(path: specification_path)!
|
||||
mut controller := new_http_controller(
|
||||
Handler: Handler{
|
||||
specification: specification
|
||||
handler: TestHandler{}
|
||||
}
|
||||
)
|
||||
spawn controller.run()
|
||||
}
|
||||
specification := new(path: specification_path)!
|
||||
mut controller := new_http_controller(
|
||||
Handler: Handler{
|
||||
specification: specification
|
||||
handler: TestHandler{}
|
||||
}
|
||||
)
|
||||
spawn controller.run()
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ module openrpc
|
||||
// Main controller for handling RPC requests
|
||||
pub struct WebSocketController {
|
||||
pub mut:
|
||||
handler Handler @[required] // Handles JSON-RPC requests
|
||||
handler Handler @[required] // Handles JSON-RPC requests
|
||||
}
|
||||
|
||||
// Creates a new HTTPController instance
|
||||
pub fn new_websocket_controller(c WebSocketController) &WebSocketController {
|
||||
return &WebSocketController{...c}
|
||||
}
|
||||
return &WebSocketController{
|
||||
...c
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,10 @@ pub fn decode(data string) !OpenRPC {
|
||||
}
|
||||
}
|
||||
|
||||
methods_any := data_map['methods'] or {return object}
|
||||
methods_any := data_map['methods'] or { return object }
|
||||
for i, method in methods_any.arr() {
|
||||
method_map := method.as_map()
|
||||
|
||||
|
||||
if result_any := method_map['result'] {
|
||||
object.methods[i].result = decode_content_descriptor_ref(result_any.as_map()) or {
|
||||
return error('Failed to decode result\n${err}')
|
||||
@@ -50,7 +50,7 @@ pub fn decode(data string) !OpenRPC {
|
||||
|
||||
fn decode_components(data_map map[string]Any) !Components {
|
||||
mut components := Components{}
|
||||
mut components_map := map[string]Any
|
||||
mut components_map := map[string]Any{}
|
||||
if components_any := data_map['components'] {
|
||||
components_map = components_any.as_map()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import freeflowuniverse.herolib.schemas.jsonschema
|
||||
const doc_path = '${os.dir(@FILE)}/testdata/openrpc.json'
|
||||
|
||||
fn test_decode() ! {
|
||||
mut doc_file := pathlib.get_file(path: openrpc.doc_path)!
|
||||
mut doc_file := pathlib.get_file(path: doc_path)!
|
||||
content := doc_file.read()!
|
||||
object := decode(content)!
|
||||
assert object.openrpc == '1.0.0-rc1'
|
||||
|
||||
@@ -14,32 +14,32 @@ const blank_openrpc = '{
|
||||
// test if encode can correctly encode a blank OpenRPC
|
||||
fn test_encode_blank() ! {
|
||||
doc := OpenRPC{
|
||||
info: Info{
|
||||
title: ''
|
||||
info: Info{
|
||||
title: ''
|
||||
version: '1.0.0'
|
||||
}
|
||||
methods: []Method{}
|
||||
}
|
||||
encoded := doc.encode()!
|
||||
assert encoded.trim_space().split_into_lines().map(it.trim_space()) == openrpc.blank_openrpc.split_into_lines().map(it.trim_space())
|
||||
assert encoded.trim_space().split_into_lines().map(it.trim_space()) == blank_openrpc.split_into_lines().map(it.trim_space())
|
||||
}
|
||||
|
||||
// test if can correctly encode an OpenRPC doc with a method
|
||||
fn test_encode_with_method() ! {
|
||||
doc := OpenRPC{
|
||||
info: Info{
|
||||
title: ''
|
||||
info: Info{
|
||||
title: ''
|
||||
version: '1.0.0'
|
||||
}
|
||||
methods: [
|
||||
Method{
|
||||
name: 'method_name'
|
||||
summary: 'summary'
|
||||
name: 'method_name'
|
||||
summary: 'summary'
|
||||
description: 'description for this method'
|
||||
deprecated: true
|
||||
params: [
|
||||
deprecated: true
|
||||
params: [
|
||||
ContentDescriptor{
|
||||
name: 'sample descriptor'
|
||||
name: 'sample descriptor'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
@@ -75,19 +75,19 @@ fn test_encode_with_method() ! {
|
||||
// test if can correctly encode a complete OpenRPC doc
|
||||
fn test_encode() ! {
|
||||
doc := OpenRPC{
|
||||
info: Info{
|
||||
title: ''
|
||||
info: Info{
|
||||
title: ''
|
||||
version: '1.0.0'
|
||||
}
|
||||
methods: [
|
||||
Method{
|
||||
name: 'method_name'
|
||||
summary: 'summary'
|
||||
name: 'method_name'
|
||||
summary: 'summary'
|
||||
description: 'description for this method'
|
||||
deprecated: true
|
||||
params: [
|
||||
deprecated: true
|
||||
params: [
|
||||
ContentDescriptor{
|
||||
name: 'sample descriptor'
|
||||
name: 'sample descriptor'
|
||||
schema: SchemaRef(Schema{
|
||||
typ: 'string'
|
||||
})
|
||||
|
||||
@@ -19,8 +19,10 @@ pub fn new(params Params) !OpenRPC {
|
||||
}
|
||||
|
||||
text := if params.path != '' {
|
||||
os.read_file(params.path)!
|
||||
} else { params.text }
|
||||
os.read_file(params.path)!
|
||||
} else {
|
||||
params.text
|
||||
}
|
||||
|
||||
return decode(text)!
|
||||
}
|
||||
return decode(text)!
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ pub struct Handler {
|
||||
pub:
|
||||
specification OpenRPC @[required] // The OpenRPC specification
|
||||
pub mut:
|
||||
handler IHandler
|
||||
handler IHandler
|
||||
}
|
||||
|
||||
pub interface IHandler {
|
||||
@@ -17,15 +17,13 @@ mut:
|
||||
@[params]
|
||||
pub struct HandleParams {
|
||||
timeout int = 60 // Timeout in seconds
|
||||
retry int // Number of retries
|
||||
retry int // Number of retries
|
||||
}
|
||||
|
||||
// Handle a JSON-RPC request and return a response
|
||||
pub fn (mut h Handler) handle(req jsonrpc.Request, params HandleParams) !jsonrpc.Response {
|
||||
// Validate the incoming request
|
||||
req.validate() or {
|
||||
return jsonrpc.new_error_response(req.id, jsonrpc.invalid_request)
|
||||
}
|
||||
req.validate() or { return jsonrpc.new_error_response(req.id, jsonrpc.invalid_request) }
|
||||
|
||||
// Check if the method exists
|
||||
if req.method == 'rpc.discover' {
|
||||
@@ -46,4 +44,4 @@ pub fn (mut h Handler) handle(req jsonrpc.Request, params HandleParams) !jsonrpc
|
||||
|
||||
// Forward the request to the custom handler
|
||||
return h.handler.handle(req)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
module openrpc
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonschema {Schema, Reference, SchemaRef, Items}
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Items, Reference, Schema, SchemaRef }
|
||||
|
||||
pub fn (s OpenRPC) inflate_method(method Method) Method {
|
||||
return Method {
|
||||
...method,
|
||||
return Method{
|
||||
...method
|
||||
params: method.params.map(ContentDescriptorRef(s.inflate_content_descriptor(it)))
|
||||
result: s.inflate_content_descriptor(method.result)
|
||||
}
|
||||
@@ -13,33 +13,41 @@ pub fn (s OpenRPC) inflate_method(method Method) Method {
|
||||
pub fn (s OpenRPC) inflate_content_descriptor(cd_ ContentDescriptorRef) ContentDescriptor {
|
||||
cd := if cd_ is Reference {
|
||||
s.components.content_descriptors[cd_.ref] as ContentDescriptor
|
||||
} else { cd_ as ContentDescriptor }
|
||||
} else {
|
||||
cd_ as ContentDescriptor
|
||||
}
|
||||
|
||||
return ContentDescriptor {
|
||||
...cd,
|
||||
return ContentDescriptor{
|
||||
...cd
|
||||
schema: s.inflate_schema(cd.schema)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (s OpenRPC) inflate_schema(schema_ref SchemaRef) Schema {
|
||||
if typeof(schema_ref).starts_with('unknown') { return Schema{}}
|
||||
if typeof(schema_ref).starts_with('unknown') {
|
||||
return Schema{}
|
||||
}
|
||||
schema := if schema_ref is Reference {
|
||||
if schema_ref.ref == '' {return Schema{}}
|
||||
if schema_ref.ref == '' {
|
||||
return Schema{}
|
||||
}
|
||||
if !schema_ref.ref.starts_with('#/components/schemas/') {
|
||||
panic('not implemented')
|
||||
}
|
||||
schema_name := schema_ref.ref.trim_string_left('#/components/schemas/')
|
||||
s.inflate_schema(s.components.schemas[schema_name])
|
||||
} else { schema_ref as Schema}
|
||||
} else {
|
||||
schema_ref as Schema
|
||||
}
|
||||
|
||||
if items := schema.items {
|
||||
return Schema {
|
||||
...schema,
|
||||
return Schema{
|
||||
...schema
|
||||
items: s.inflate_items(items)
|
||||
}
|
||||
}
|
||||
return Schema {
|
||||
...schema,
|
||||
return Schema{
|
||||
...schema
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,4 +58,4 @@ pub fn (s OpenRPC) inflate_items(items Items) Items {
|
||||
its := Items(SchemaRef(s.inflate_schema(items as SchemaRef)))
|
||||
return its
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module openrpc
|
||||
|
||||
import x.json2 as json {Any}
|
||||
import x.json2 as json { Any }
|
||||
import freeflowuniverse.herolib.schemas.jsonschema { Reference, SchemaRef }
|
||||
|
||||
// This is the root object of the OpenRPC document.
|
||||
@@ -122,7 +122,7 @@ pub:
|
||||
name string @[omitempty] // Cannonical name of the example.
|
||||
summary string @[omitempty] // Short description for the example.
|
||||
description string @[omitempty] // A verbose explanation of the example.
|
||||
value Any @[omitempty; json: '-'] // Embedded literal example. The value field and externalValue field are mutually exclusive. To represent examples of media types that cannot naturally represented in JSON, use a string value to contain the example, escaping where necessary.
|
||||
value Any @[json: '-'; omitempty] // Embedded literal example. The value field and externalValue field are mutually exclusive. To represent examples of media types that cannot naturally represented in JSON, use a string value to contain the example, escaping where necessary.
|
||||
external_value string @[json: externalValue; omitempty] // A URL that points to the literal example. This provides the capability to reference examples that cannot easily be included in JSON documents. The value field and externalValue field are mutually exclusive.
|
||||
}
|
||||
|
||||
@@ -156,8 +156,8 @@ pub type ErrorRef = ErrorSpec | Reference
|
||||
// Defines an application level error.
|
||||
pub struct ErrorSpec {
|
||||
pub:
|
||||
code int // A Number that indicates the error type that occurred. This MUST be an integer. The error codes from and including -32768 to -32000 are reserved for pre-defined errors. These pre-defined errors SHOULD be assumed to be returned from any JSON-RPC api.
|
||||
message string // A String providing a short description of the error. The message SHOULD be limited to a concise single sentence.
|
||||
code int // A Number that indicates the error type that occurred. This MUST be an integer. The error codes from and including -32768 to -32000 are reserved for pre-defined errors. These pre-defined errors SHOULD be assumed to be returned from any JSON-RPC api.
|
||||
message string // A String providing a short description of the error. The message SHOULD be limited to a concise single sentence.
|
||||
data SchemaRef // A Primitive or Structured value that contains additional information about the error. This may be omitted. The value of this member is defined by the Server (e.g. detailed error information, nested errors etc.).
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ pub fn parse_struct_example(structure Struct) Example {
|
||||
val_map[field.name] = example_val
|
||||
}
|
||||
return Example{
|
||||
name: '${structure.name}Example'
|
||||
name: '${structure.name}Example'
|
||||
value: json2.encode(val_map)
|
||||
}
|
||||
}
|
||||
@@ -56,12 +56,12 @@ pub fn parse_pairing_params(text_ string) []ExampleRef {
|
||||
examples << Reference{text}
|
||||
} else if text.contains(':') {
|
||||
examples << Example{
|
||||
name: ''
|
||||
name: ''
|
||||
value: json2.encode(text)
|
||||
}
|
||||
} else {
|
||||
examples1 := text.split(',').map(it.trim_space()).map(ExampleRef(Example{
|
||||
name: ''
|
||||
name: ''
|
||||
value: it
|
||||
}))
|
||||
|
||||
@@ -77,7 +77,7 @@ pub fn parse_pairing_result(text_ string) ExampleRef {
|
||||
return Reference{text}
|
||||
} else if text.contains(':') {
|
||||
return Example{
|
||||
name: ''
|
||||
name: ''
|
||||
value: json2.encode(text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ assert some_function('input_string') == 'output_string'
|
||||
// }
|
||||
|
||||
fn test_parse_example_pairing() ! {
|
||||
example := parse_example_pairing(openrpc.example_txt)!
|
||||
example := parse_example_pairing(example_txt)!
|
||||
params := example.params
|
||||
assert params.len == 1
|
||||
param0 := (params[0] as Example)
|
||||
@@ -36,23 +36,23 @@ fn test_parse_example_pairing() ! {
|
||||
}
|
||||
|
||||
const test_struct = Struct{
|
||||
name: 'TestStruct'
|
||||
name: 'TestStruct'
|
||||
fields: [
|
||||
StructField{
|
||||
name: 'TestField'
|
||||
typ: Type{
|
||||
name: 'TestField'
|
||||
typ: Type{
|
||||
symbol: 'int'
|
||||
}
|
||||
attrs: [Attribute{
|
||||
name: 'example'
|
||||
arg: '21'
|
||||
arg: '21'
|
||||
}]
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn test_parse_struct_example() ! {
|
||||
example := parse_struct_example(openrpc.test_struct)
|
||||
example := parse_struct_example(test_struct)
|
||||
// assert example.name == 'TestStructExample'
|
||||
// panic(example)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub struct Playground {
|
||||
@[params]
|
||||
pub struct PlaygroundConfig {
|
||||
pub:
|
||||
dest pathlib.Path @[required]
|
||||
dest pathlib.Path @[required]
|
||||
specs []pathlib.Path
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ pub fn export_playground(config PlaygroundConfig) ! {
|
||||
const build_path = '${os.dir(@FILE)}/playground'
|
||||
|
||||
pub fn new_playground(config PlaygroundConfig) !&Playground {
|
||||
build_dir := pathlib.get_dir(path: openrpc.build_path)!
|
||||
build_dir := pathlib.get_dir(path: build_path)!
|
||||
mut pg := Playground{
|
||||
build: build_dir
|
||||
}
|
||||
@@ -72,7 +72,7 @@ fn encode_env(specs_ []pathlib.Path) !string {
|
||||
name := texttools.name_fix(o.info.title)
|
||||
examples << ExampleSpec{
|
||||
name: name
|
||||
url: '/specs/${name}.json'
|
||||
url: '/specs/${name}.json'
|
||||
}
|
||||
}
|
||||
mut examples_str := "window._env_ = { ACTORS: '${json.encode(examples)}' }"
|
||||
|
||||
Reference in New Issue
Block a user