Files
supermq/pkg/sdk/sdk.go
T
2025-08-26 13:26:32 +02:00

353 lines
10 KiB
Go

// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package sdk
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/absmach/supermq/pkg/errors"
smqSDK "github.com/absmach/supermq/pkg/sdk"
"moul.io/http2curl"
)
var _ SDK = (*mgSDK)(nil)
type Metadata map[string]any
type PageMetadata struct {
Total uint64 `json:"total"`
Offset uint64 `json:"offset"`
Limit uint64 `json:"limit"`
Metadata Metadata `json:"metadata,omitempty"`
Topic string `json:"topic,omitempty"`
Contact string `json:"contact,omitempty"`
DomainID string `json:"domain_id,omitempty"`
Level uint64 `json:"level,omitempty"`
State string `json:"state,omitempty"`
Name string `json:"name,omitempty"`
}
type MessagePageMetadata struct {
PageMetadata
Subtopic string `json:"subtopic,omitempty"`
Publisher string `json:"publisher,omitempty"`
Limit int `json:"limit,omitempty"`
Name string `json:"name,omitempty"`
Comparator string `json:"comparator,omitempty"`
BoolValue *bool `json:"vb,omitempty"`
StringValue string `json:"vs,omitempty"`
DataValue string `json:"vd,omitempty"`
From float64 `json:"from,omitempty"`
To float64 `json:"to,omitempty"`
Aggregation string `json:"aggregation,omitempty"`
Interval string `json:"interval,omitempty"`
Value float64 `json:"value,omitempty"`
Protocol string `json:"protocol,omitempty"`
}
// SDK contains Magistrala API.
type SDK interface {
smqSDK.SDK
// AddBootstrap add bootstrap configuration
//
// example:
// cfg := sdk.BootstrapConfig{
// ClientID: "clientID",
// Name: "bootstrap",
// ExternalID: "externalID",
// ExternalKey: "externalKey",
// Channels: []string{"channel1", "channel2"},
// }
// id, _ := sdk.AddBootstrap(ctx, cfg, "domainID", "token")
// fmt.Println(id)
AddBootstrap(ctx context.Context, cfg BootstrapConfig, domainID, token string) (string, errors.SDKError)
// View returns Client Config with given ID belonging to the user identified by the given token.
//
// example:
// bootstrap, _ := sdk.ViewBootstrap(ctx, "id", "domainID", "token")
// fmt.Println(bootstrap)
ViewBootstrap(ctx context.Context, id, domainID, token string) (BootstrapConfig, errors.SDKError)
// Update updates editable fields of the provided Config.
//
// example:
// cfg := sdk.BootstrapConfig{
// ClientID: "clientID",
// Name: "bootstrap",
// ExternalID: "externalID",
// ExternalKey: "externalKey",
// Channels: []string{"channel1", "channel2"},
// }
// err := sdk.UpdateBootstrap(ctx, cfg, "domainID", "token")
// fmt.Println(err)
UpdateBootstrap(ctx context.Context, cfg BootstrapConfig, domainID, token string) errors.SDKError
// Update bootstrap config certificates.
//
// example:
// err := sdk.UpdateBootstrapCerts(ctx, "id", "clientCert", "clientKey", "ca", "domainID", "token")
// fmt.Println(err)
UpdateBootstrapCerts(ctx context.Context, id string, clientCert, clientKey, ca string, domainID, token string) (BootstrapConfig, errors.SDKError)
// UpdateBootstrapConnection updates connections performs update of the channel list corresponding Client is connected to.
//
// example:
// err := sdk.UpdateBootstrapConnection(ctx, "id", []string{"channel1", "channel2"}, "domainID", "token")
// fmt.Println(err)
UpdateBootstrapConnection(ctx context.Context, id string, channels []string, domainID, token string) errors.SDKError
// Remove removes Config with specified token that belongs to the user identified by the given token.
//
// example:
// err := sdk.RemoveBootstrap(ctx, "id", "domainID", "token")
// fmt.Println(err)
RemoveBootstrap(ctx context.Context, id, domainID, token string) errors.SDKError
// Bootstrap returns Config to the Client with provided external ID using external key.
//
// example:
// bootstrap, _ := sdk.Bootstrap(ctx, "externalID", "externalKey")
// fmt.Println(bootstrap)
Bootstrap(ctx context.Context, externalID, externalKey string) (BootstrapConfig, errors.SDKError)
// BootstrapSecure retrieves a configuration with given external ID and encrypted external key.
//
// example:
// bootstrap, _ := sdk.BootstrapSecure(ctx, "externalID", "externalKey", "cryptoKey")
// fmt.Println(bootstrap)
BootstrapSecure(ctx context.Context, externalID, externalKey, cryptoKey string) (BootstrapConfig, errors.SDKError)
// Bootstraps retrieves a list of managed configs.
//
// example:
// pm := sdk.PageMetadata{
// Offset: 0,
// Limit: 10,
// }
// bootstraps, _ := sdk.Bootstraps(ctx, pm, "domainID", "token")
// fmt.Println(bootstraps)
Bootstraps(ctx context.Context, pm PageMetadata, domainID, token string) (BootstrapPage, errors.SDKError)
// Whitelist updates Client state Config with given ID belonging to the user identified by the given token.
//
// example:
// err := sdk.Whitelist(ctx, "clientID", 1, "domainID", "token")
// fmt.Println(err)
Whitelist(ctx context.Context, clientID string, state int, domainID, token string) errors.SDKError
// ReadMessages read messages of specified channel.
//
// example:
// pm := sdk.MessagePageMetadata{
// Offset: 0,
// Limit: 10,
// }
// msgs, _ := sdk.ReadMessages(ctx, pm,"channelID", "domainID", "token")
// fmt.Println(msgs)
ReadMessages(ctx context.Context, pm MessagePageMetadata, chanID, domainID, token string) (MessagesPage, errors.SDKError)
// CreateSubscription creates a new subscription
//
// example:
// subscription, _ := sdk.CreateSubscription(ctx, "topic", "contact", "token")
// fmt.Println(subscription)
CreateSubscription(ctx context.Context, topic, contact, token string) (string, errors.SDKError)
// ListSubscriptions list subscriptions given list parameters.
//
// example:
// pm := sdk.PageMetadata{
// Offset: 0,
// Limit: 10,
// }
// subscriptions, _ := sdk.ListSubscriptions(ctx, pm, "token")
// fmt.Println(subscriptions)
ListSubscriptions(ctx context.Context, pm PageMetadata, token string) (SubscriptionPage, errors.SDKError)
// ViewSubscription retrieves a subscription with the provided id.
//
// example:
// subscription, _ := sdk.ViewSubscription(ctx, "id", "token")
// fmt.Println(subscription)
ViewSubscription(ctx context.Context, id, token string) (Subscription, errors.SDKError)
// DeleteSubscription removes a subscription with the provided id.
//
// example:
// err := sdk.DeleteSubscription(ctx, "id", "token")
// fmt.Println(err)
DeleteSubscription(ctx context.Context, id, token string) errors.SDKError
}
type mgSDK struct {
bootstrapURL string
readersURL string
usersURL string
client *http.Client
curlFlag bool
msgContentType smqSDK.ContentType
smqSDK.SDK
}
// Config contains sdk configuration parameters.
type Config struct {
BootstrapURL string
CertsURL string
HTTPAdapterURL string
ReaderURL string
ClientsURL string
UsersURL string
GroupsURL string
ChannelsURL string
DomainsURL string
JournalURL string
HostURL string
MsgContentType smqSDK.ContentType
TLSVerification bool
CurlFlag bool
}
// NewSDK returns new supermq SDK instance.
func NewSDK(conf Config) SDK {
smqSDK := smqSDK.NewSDK(smqSDK.Config{
CertsURL: conf.CertsURL,
HTTPAdapterURL: conf.HTTPAdapterURL,
ClientsURL: conf.ClientsURL,
UsersURL: conf.UsersURL,
GroupsURL: conf.GroupsURL,
ChannelsURL: conf.ChannelsURL,
DomainsURL: conf.DomainsURL,
JournalURL: conf.JournalURL,
HostURL: conf.HostURL,
MsgContentType: conf.MsgContentType,
TLSVerification: conf.TLSVerification,
CurlFlag: conf.CurlFlag,
})
return &mgSDK{
bootstrapURL: conf.BootstrapURL,
readersURL: conf.ReaderURL,
usersURL: conf.UsersURL,
msgContentType: conf.MsgContentType,
client: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: !conf.TLSVerification,
},
},
},
curlFlag: conf.CurlFlag,
SDK: smqSDK,
}
}
// processRequest creates and send a new HTTP request, and checks for errors in the HTTP response.
// It then returns the response headers, the response body, and the associated error(s) (if any).
func (sdk mgSDK) processRequest(ctx context.Context, method, reqUrl, token string, data []byte, headers map[string]string, expectedRespCodes ...int) (http.Header, []byte, errors.SDKError) {
req, err := http.NewRequestWithContext(ctx, method, reqUrl, bytes.NewReader(data))
if err != nil {
return make(http.Header), []byte{}, errors.NewSDKError(err)
}
// Sets a default value for the Content-Type.
// Overridden if Content-Type is passed in the headers arguments.
req.Header.Add("Content-Type", string(smqSDK.CTJSON))
for key, value := range headers {
req.Header.Add(key, value)
}
if token != "" {
if !strings.Contains(token, smqSDK.ClientPrefix) {
token = smqSDK.BearerPrefix + token
}
req.Header.Set("Authorization", token)
}
if sdk.curlFlag {
curlCommand, err := http2curl.GetCurlCommand(req)
if err != nil {
return nil, nil, errors.NewSDKError(err)
}
log.Println(curlCommand.String())
}
resp, err := sdk.client.Do(req)
if err != nil {
return make(http.Header), []byte{}, errors.NewSDKError(err)
}
defer resp.Body.Close()
sdkerr := errors.CheckError(resp, expectedRespCodes...)
if sdkerr != nil {
return make(http.Header), []byte{}, sdkerr
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return make(http.Header), []byte{}, errors.NewSDKError(err)
}
return resp.Header, body, nil
}
func (sdk mgSDK) withQueryParams(baseURL, endpoint string, pm PageMetadata) (string, error) {
q, err := pm.query()
if err != nil {
return "", err
}
return fmt.Sprintf("%s/%s?%s", baseURL, endpoint, q), nil
}
func (pm PageMetadata) query() (string, error) {
q := url.Values{}
if pm.Offset != 0 {
q.Add("offset", strconv.FormatUint(pm.Offset, 10))
}
if pm.Limit != 0 {
q.Add("limit", strconv.FormatUint(pm.Limit, 10))
}
if pm.Total != 0 {
q.Add("total", strconv.FormatUint(pm.Total, 10))
}
if pm.Metadata != nil {
md, err := json.Marshal(pm.Metadata)
if err != nil {
return "", errors.NewSDKError(err)
}
q.Add("metadata", string(md))
}
if pm.Topic != "" {
q.Add("topic", pm.Topic)
}
if pm.Contact != "" {
q.Add("contact", pm.Contact)
}
if pm.DomainID != "" {
q.Add("domain_id", pm.DomainID)
}
if pm.Level != 0 {
q.Add("level", strconv.FormatUint(pm.Level, 10))
}
return q.Encode(), nil
}