# _{{stinger.title}}_ API Overview 
{%-if stinger.summary %} 
{{stinger.summary|italics}}
{%endif-%}
{%if stinger.documentation%}
{{stinger.documentation}}
{%endif%}

[[_TOC_]]

## Connections

A connection object is a wrapper around an MQTT client and provides specific functionality to support both clients and servers.
Generally, you only need one connection object per daemon/program, as it can support multiple clients and servers.  

### Connection code Examples

<details>
  <summary>Python</summary>

```python
from connection import MqttBrokerConnection, MqttTransportType, MqttTransport

transport = MqttTransport(MqttTransportType.TCP, "localhost", 1883) # Or: MqttTransport(MqttTransportType.UNIX, socket_path="/path/to/socket")
connection_object = MqttBrokerConnection(transport)
```

The `connection_object` will be passed to client and server constructors.

</details>

<details>
  <summary>Rust</summary>

Rust implementations use the [MQTTier](https://crates.io/crates/mqttier) crate for MQTT connectivity.  MQTTier is a wrapper around the [rumqttc](https://crates.io/crates/rumqttc) crate and handles serialization, message queuing, and acknowledgments.

```rust
use mqttier::{MqttierClient, MqttierOptions};

  let conn_opts = MqttierOptionsBuilder::new()
      .connection(Connection::TcpLocalhost(1883)) // Connection::UnixSocket("/path/to/socket") is also supported.
      .build()
      .unwrap()
      .expect("Failed to build MQTT connection options");
  let mut connection = MqttierClient::new(conn_opts).unwrap().expect("Failed to create MQTT client");
```

The `connection_object` will be passed to client and server constructors.

</details>

<details>
  <summary>C++</summary>

The C++ connection object is a wrapper around the [libmosquitto](https://mosquitto.org/api/files/mosquitto-h.html) C library.  This library only supports TCP and WebSocket connections.  Unix Domain Socket support may be added in the future.

```c++
#include "broker.hpp"

auto connection_object = std::make_shared<MqttBrokerConnection>("localhost", 1883, "daemon-name");
```

The `connection_object` will be passed to client and server constructors.

</details>

## Server

A server is a _provider_ of functionality.  It sends signals, handles method calls, and owns property values.

### Server Code Examples

<details>
  <summary>Python Server</summary>

```python
from {{stinger.python.package_name}}.client import {{stinger.cpp.server_class_name}}

server = {{stinger.python.server_class_name}}(connection_object)
```

The `server` object provides methods for emitting signals and updating properties.  It also allows for decorators to indicate method call handlers.

A full example can be viewed by looking at the `if __name__ == "__main__":` section of the generated `{{stinger.python.package_name}}.server.py` module.

</details>


<details>
  <summary>C++ Server</summary>

```c++

```

The `server` object provides methods for emitting signals and updating properties.  It also allows for decorators to indicate method call handlers.

A full example can be viewed by looking at the generated `examples/server_main.cpp` file.`

</details>

## Client

A client is a _utilizer_ of functionality.  It receives signals, makes method calls, reads property values, or requests updates to property values.

<details>
  <summary>Rust</summary>

```rust
let {%if stinger.methods|length > 0%}mut {%endif%}api_client = {{stinger.rust.client_struct_name}}::new(&mut connection).await;
```

A full example can be viewed by looking at the generated `client/examples/client.rs` file.

</details>

<details>
  <summary>C++ Client</summary>

A full example can be viewed by looking at the generated `examples/client_main.cpp` file.

</details>

## Logging

Each generated language has different ways of handling logging.  

### Python

Python uses the standard Python `logging` module.  

### Rust

Rust uses the `tracing` crate for logging.

### C++

C++ uses a user-provided logging function.  The function should take two parameters: an integer log level and a string message. 

Log levels are re-used from the `syslog.h` header file, although no other syslog mechanisms are used.  Client and server classes use the logging provided by the `MqttBrokerConnection` object.

<details>
  <summary>Example C++ Code</summary>

```c++
#include <syslog.h>

auto connnection = std::make_shared<MqttBrokerConnection>(...);
connnection->SetLogLevel(LOG_DEBUG);
connnection->SetLogFunction([](int level, const char* msg)
{
    std::cout << "[" << level << "] " << msg << std::endl;
});
```

</details>

{%macro argrow(arg) -%}
|{{arg.name|center(15)}}|
{%-if arg.arg_type.name.lower() == "enum" or arg.arg_type.name.lower() == "struct"%}{{arg.markdown_type|center(10)}}
{%-else%}{{arg.json_type|center(10)}}
{%-endif%}{%if arg.optional%} (optional){%endif%}|
{{- arg.description or ''}}|
{%- endmacro %}
{%macro argtable(arglist) -%}
| Name          | Type     |Description|
|---------------|----------|-----------|
{%-for arg in arglist%}
{{argrow(arg)}}
{%- endfor %}
{%- endmacro %}
{%if stinger.signals | length > 0 %}
## Signals

Signals are messages from a server to clients.

```plantuml
@startuml
Client <<- Server : Signal(Parameters)
@enduml
```
{%for sig_name, signal in stinger.signals.items()%}
### Signal `{{sig_name}}`

{{signal.documentation or "_No documentation for this signal_"}}

#### Signal Parameters for `{{sig_name}}`

{{argtable(signal.arg_list)}}

#### Code Examples

<details>
  <summary>Python Client</summary>

The `{{sig_name}}` signal can be subscribed to by using the client's `receive_{{sig_name | snake_case }}` decorator on a callback function. The name of the function does not matter. The function is called any time the signal is received.

```python
@client.receive_{{sig_name | snake_case }}
def on_{{sig_name | snake_case }}({%for arg in signal.arg_list%}{{arg.name}}: {{arg.python_annotation}}{%if not loop.last%}, {%endif%}{%endfor%}):
    print(f"Got a '{{sig_name}}' signal: {%for arg in signal.arg_list%}{{arg.name}}={ {{arg.name}} } {%endfor%}")
```

</details>

<details>
  <summary>Python Server</summary>

A server can emit a `{{sig_name}}` signal simply by calling the server's `emit_{{sig_name | snake_case}}` method.

```python
server.emit_{{sig_name | snake_case}}({%for arg in signal.arg_list%}{{arg.get_random_example_value(lang="python")}}{%if not loop.last%}, {%endif%}{%endfor%})
```

</details>

<details>
  <summary>Rust Client</summary>

A Rust client receives signals through a `tokio::broadcast` channel.  Receiving from the channel returns a `Result<T, RecvError>` object.  

Since receiving a message through the channel blocks, it may be best to put this into a separate async task.

```rust
let mut {{sig_name|snake_case}}_signal_rx = client.get_{{sig_name|snake_case}}_receiver();
print("Got a '{{sig_name}}' signal: {:?}", {{sig_name|snake_case}}_signal_rx.recv().await);
```

</details>

<details>
  <summary>Rust Server</summary>

A server can emit a `{{sig_name}}` signal simply by calling the server's `emit_{{sig_name | snake_case}}` method.

```rust
let publish_result = server.emit_{{sig_name|snake_case}}({%for arg in signal.arg_list%}{{arg.get_random_example_value(lang="rust")}}{%if not loop.last%}, {%endif%}{%endfor%}).await;
```

The return type is a **Pinned Boxed Future** that resolves to a `Result<(), MethodReturnCode>`.  The future is resolved when the signal is sent (with "publish complete" acknowledgment) or when an error occurs.  If you need to block until the signal is received by the MQTT broker, you can `.await` the future.

</details>

<details>
  <summary>C++ Client</summary>

A client can register a callback function to be called when a `{{sig_name}}` signal is received.  The callback function should take the same parameters as the signal.  In this example, we are using a lambda as the callback function.

```cpp
client.register{{sig_name | UpperCamelCase}}Callback([]({%for arg in signal.arg_list%}{{arg.cpp_type}} {{arg.name}}{%if not loop.last%}, {%endif%}{%endfor%}) {
    std::cout << {%for arg in signal.arg_list%}"{{arg.name}}=" << {%-if arg.optional%} "None"{%else%}{%if arg.arg_type.name.lower() == 'enum'%}{{arg.enum.name | camelCase }}Strings[static_cast<int>({{arg.name}})]{%else%}{{arg.name}}{%endif%}{%endif%} << {%if not loop.last %}" | " << {%endif%}{%endfor%} std::endl;
});
```

</details>

<details>
  <summary>C++ Server</summary>

A `{{sig_name}}` signal can be emitted by calling the server's `emit{{sig_name | UpperCamelCase}}Signal` method.  This returns a `std::future` that can be waited on if desired.  The future is resolved when the signal is sent.

```cpp
auto {{sig_name|lowerCamelCase}}Future = server.emit{{sig_name | UpperCamelCase}}Signal({%for arg in signal.arg_list%}{{arg.get_random_example_value(lang="c++")}}{%if not loop.last%}, {%endif%}{%endfor%});
{{sig_name|lowerCamelCase}}Future.wait(); // Optional, to block until signal is sent.
```

</details>

{%endfor%}{#- end for each signal -#}
{%endif%}{#- end if there are signals -#}

{#- ------------------------------------------------------- -#}
{%if stinger.methods | length > 0 %}
## Methods

Methods are requests from a client to a server and the server provides a response back to the client:

```plantuml
@startuml
Client ->> Server : Request(Parameters)
Client <<-- Server: Response(Parameters)
@enduml
```

{%for method_name, method in stinger.methods.items()%}
### Method `{{method_name}}`

{{method.documentation or "_No documentation for this method_"}}

#### Request Parameters
{%if method.arg_list | length == 0%}
There are no arguments for this request.
{%else-%}
{{argtable(method.arg_list)}}
{%endif%}{# number of args #}
#### Return Parameters

{%if method.return_value_type == "struct" %}
{{argtable(method.return_arg_list)}}
{%elif method.return_value_type is false -%}
There is no return value for this method call.
{%else-%}
The return value type is `{{method.return_value.json_type}}`.
{%endif-%}

#### Code Examples

<details>
  <summary>Python Client</summary>

The `{{method_name}}` method can be called by calling the clients's `{{method_name|snake_case}}` method.
This returns a `Future` object.  In this example, we wait up to 5 seconds for the result.

```python
from futures import Future

future = client.{{method_name|snake_case}}({%for arg in method.arg_list%}{{arg.name}}={{arg.get_random_example_value()}}{%if not loop.last%}, {%endif%}{%endfor%})
try:
    print(f"RESULT:  {future.result(5)}")
except futures.TimeoutError:
    print(f"Timed out waiting for response to '{{method_name|snake_case}}' call")
```

</details>

<details>
  <summary>Python Server</summary>

The server provides an implementation for the `{{method_name}}` method by using the `@server.handle_{{method_name|snake_case}}` decorator on a function.  The name of the function does not matter. 
The decorated method is called everytime the a request for the method is received.  In an error, the method can raise on of the exceptions found in `method_codes.py`.

```python
@server.handle_{{method_name | snake_case}} 
def {{method_name | snake_case}}({%for arg in method.arg_list%}{{arg.name}}: {{arg.python_annotation}}{%if not loop.last%}, {%endif%}{%endfor%}) -> {{method.return_value_python_annotation}}:
    """ This is an example handler for the '{{method_name}}' method.  """
    print(f"Running {{method_name | snake_case}}'({%for arg in method.arg_list %}{ {{-arg.name-}} }{%if not loop.last%}, {%endif%}{%endfor%})'")
    return {{method.get_return_value_random_example_value('python')}}
```

</details>

<details>
  <summary>Rust Client</summary>

The `{{stinger.rust.client_struct_name}}` provides an implementation for the `{{method_name}}` method.  It will block and return a Result object of either the return payload value, or an error.

```rust
let result = api_client.{{method_name | snake_case}}({%for arg in method.arg_list%}{{arg.get_random_example_value(lang='rust')}}{%if not loop.last%}, {%endif%}{%endfor%}).await.expect("Failed to call {{method_name}}");
println!("{{method_name}} response: {:?}", result);
```

</details>

{%endfor-%}
{%endif%}
{#- ------------------------------------------------------- #}
{%if stinger.properties | length > 0 %}## Properties

Properties are values (or a set of values) held by the server.   They are re-published when the value changes. 

```plantuml
@startuml
Server -> Server : Set Property
Client <<- Server: Property Updated
@enduml
```
{%for prop_name, prop in stinger.properties.items()%}
### Property `{{prop_name}}`

{{prop.documentation or "_No documentation is available for this property_"}}{%if prop.read_only %}

This property is **read-only**.  It can only be modified by the server.{%endif%}

{{argtable(prop.arg_list)}}

### Code Examples

<details>
  <summary>Rust Server</summary>

A server hold the "source of truth" for the value of `{{prop_name}}`.  The value can be changed by calling the server's `set_{{prop_name | snake_case}}` method:

```rust
let property_set_future: SentMessageFuture = server.set_{{prop_name | snake_case}}({{prop.arg_list[0].get_random_example_value(lang="rust")}}).await;
```

The return type is a **Pinned Boxed Future** that resolves to a `Result<(), MethodReturnCode>`. 
The future is resolved with `Ok(())` if the value didn't change or when the MQTT broker responds with a "publish acknowledgment" on the publishing of the updated value.  Otherwise, the future resolves to an error code.

The application code should call the `set_{{prop_name | snake_case}}()` method with an initial value when starting up, and then whenever the value changes.
{%if not prop.read_only %}
The property can also be changed by a client request via MQTT.  When this happens, the server will send to a `tokio::watch` channel with the updated property value.
Application code can get a `watch::Receiver<Option<{{prop.rust_type}}>>` by calling the server's `get_{{prop_name | snake_case}}_receiver()` method.  The receiver can be used to get the current value of the property, and to be notified when the value changes.

```rust
let mut on_{{prop_name|snake_case}}_changed = server.watch_{{prop_name|snake_case}}();

while let Some(new_value) = on_{{prop_name|snake_case}}_changed.recv().await {
    println!("Property '{{prop_name}}' changed to: {:?}", new_value);
}
```
{%endif%}
</details>

{%endfor%}{# for each property #}
{%endif%}{#end condition that there are a number of properties#}
{%if stinger.enums | length > 0%}## Enums
{%endif%}

{%for ie_name, ie in stinger.enums.items() %}
{#- ------------------------------------------------------- -#}
### Enum `{{ie.class_name }}`

<a name="Enum-{{ie.class_name}}"></a>{{ie.description or "_No description exists for this enumeration._"}}

{%for value in ie.values -%}
* {{value}} ({{loop.index}}) 
{%- if ie.value_description(loop.index) %}
  - {{ie.value_description(loop.index)}}{% endif %}
{%endfor%}
{%endfor%}
{#- ------------------------------------------------------- #}
## Structures

Structures are a group of values and may be used as an argument in signals, methods, or properties.  Defining a structure allows for easy reuse.
{%for istruct_name, istruct in stinger.structs.items() %}
### Struct `{{istruct.class_name }}`

<a name="Struct-{{istruct.class_name}}"></a>{{istruct.description or "_No general description exists for this structure_"}}

{{argtable(istruct.members)}}
{%endfor%}
