Merge branch 'development_actions007' of github.com:freeflowuniverse/herolib into development_actions007

This commit is contained in:
Timur Gordon
2025-03-26 19:27:57 +01:00
375 changed files with 8342 additions and 9011 deletions

View File

@@ -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()!
}
}

View File

@@ -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) {

View File

@@ -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')

View File

@@ -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
}
})!

View File

@@ -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
}
}

View File

@@ -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
}
}
}

View File

@@ -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)
}
}

View File

@@ -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"')
}

View File

@@ -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{} }
}

View File

@@ -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'
}
}

View File

@@ -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()
}

View File

@@ -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))
}
}

View File

@@ -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())
}
}

View File

@@ -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')

View File

@@ -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{}
},
]
}

View File

@@ -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.'
}

View File

@@ -48,4 +48,4 @@ pub fn decode_schemaref(data_map map[string]Any) !SchemaRef {
}
}
return decode(data_map.str())!
}
}

View File

@@ -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']
}
}

View File

@@ -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: '-']
}

View File

@@ -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}}'
}
}

View File

@@ -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")'
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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'))
}

View File

@@ -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()
}
}

View File

@@ -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'
}

View File

@@ -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}'
}

View File

@@ -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)
}
}

View File

@@ -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 servers URL template.
variables map[string]ServerVariable @[omitempty] // A map between a variable name and its value. The value is used for substitution in the servers 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 Objects 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 Objects 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 Objects 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 Objects 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 Objects 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 Objects 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 {}

View File

@@ -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,
]
}
}

View 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
}
}

View 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 {

View File

@@ -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,

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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()
}

View File

@@ -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
}
}

View File

@@ -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()
}

View File

@@ -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'

View File

@@ -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'
})

View File

@@ -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)!
}

View File

@@ -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)
}
}

View File

@@ -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
}
}
}

View File

@@ -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.).
}

View File

@@ -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)
}
}

View File

@@ -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)
}

View File

@@ -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)}' }"