Skip to content

Commit 28dbead

Browse files
authored
feat: ✨ Add no-sqldb service (#26)
1 parent 118c826 commit 28dbead

File tree

2 files changed

+585
-0
lines changed

2 files changed

+585
-0
lines changed

config/pwrnosqldb/power_kv.go

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
package pwrnosqldb
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/hex"
7+
"encoding/json"
8+
"fmt"
9+
"io"
10+
"net/http"
11+
"net/url"
12+
"strconv"
13+
"strings"
14+
"time"
15+
)
16+
17+
// PowerKvError represents errors from PowerKv operations
18+
type PowerKvError struct {
19+
Type string
20+
Message string
21+
}
22+
23+
func (e *PowerKvError) Error() string {
24+
return fmt.Sprintf("%s: %s", e.Type, e.Message)
25+
}
26+
27+
// NewPowerKvError creates a new PowerKvError
28+
func NewPowerKvError(errorType, message string) *PowerKvError {
29+
return &PowerKvError{Type: errorType, Message: message}
30+
}
31+
32+
// StoreDataRequest represents the JSON payload for storing data
33+
type StoreDataRequest struct {
34+
ProjectID string `json:"projectId"`
35+
Secret string `json:"secret"`
36+
Key string `json:"key"`
37+
Value string `json:"value"`
38+
}
39+
40+
// GetValueResponse represents the JSON response for getting data
41+
type GetValueResponse struct {
42+
Value string `json:"value"`
43+
}
44+
45+
// ErrorResponse represents error responses from the server
46+
type ErrorResponse struct {
47+
Message string `json:"message"`
48+
}
49+
50+
// PowerKv represents the basic PowerKv client
51+
type PowerKv struct {
52+
client *http.Client
53+
serverURL string
54+
projectID string
55+
secret string
56+
}
57+
58+
// NewPowerKv creates a new PowerKv instance
59+
func NewPowerKv(projectID, secret string) (*PowerKv, error) {
60+
if strings.TrimSpace(projectID) == "" {
61+
return nil, NewPowerKvError("InvalidInput", "Project ID cannot be null or empty")
62+
}
63+
if strings.TrimSpace(secret) == "" {
64+
return nil, NewPowerKvError("InvalidInput", "Secret cannot be null or empty")
65+
}
66+
67+
client := &http.Client{
68+
Timeout: 10 * time.Second,
69+
}
70+
71+
return &PowerKv{
72+
client: client,
73+
serverURL: "https://pwrnosqlvida.pwrlabs.io/",
74+
projectID: projectID,
75+
secret: secret,
76+
}, nil
77+
}
78+
79+
// GetServerURL returns the server URL
80+
func (p *PowerKv) GetServerURL() string {
81+
return p.serverURL
82+
}
83+
84+
// GetProjectID returns the project ID
85+
func (p *PowerKv) GetProjectID() string {
86+
return p.projectID
87+
}
88+
89+
// toHexString converts bytes to hex string
90+
func (p *PowerKv) toHexString(data []byte) string {
91+
return hex.EncodeToString(data)
92+
}
93+
94+
// fromHexString converts hex string to bytes
95+
func (p *PowerKv) fromHexString(hexString string) ([]byte, error) {
96+
// Handle both with and without 0x prefix
97+
if strings.HasPrefix(hexString, "0x") || strings.HasPrefix(hexString, "0X") {
98+
hexString = hexString[2:]
99+
}
100+
101+
data, err := hex.DecodeString(hexString)
102+
if err != nil {
103+
return nil, NewPowerKvError("HexDecodeError", fmt.Sprintf("Invalid hex: %v", err))
104+
}
105+
return data, nil
106+
}
107+
108+
// toBytes converts various types to bytes
109+
func (p *PowerKv) toBytes(data interface{}) ([]byte, error) {
110+
if data == nil {
111+
return nil, NewPowerKvError("InvalidInput", "Data cannot be nil")
112+
}
113+
114+
switch v := data.(type) {
115+
case []byte:
116+
return v, nil
117+
case string:
118+
return []byte(v), nil
119+
case int:
120+
return []byte(strconv.Itoa(v)), nil
121+
case int32:
122+
return []byte(strconv.FormatInt(int64(v), 10)), nil
123+
case int64:
124+
return []byte(strconv.FormatInt(v, 10)), nil
125+
case float32:
126+
return []byte(strconv.FormatFloat(float64(v), 'f', -1, 32)), nil
127+
case float64:
128+
return []byte(strconv.FormatFloat(v, 'f', -1, 64)), nil
129+
default:
130+
return nil, NewPowerKvError("InvalidInput", "Data must be []byte, string, or number")
131+
}
132+
}
133+
134+
// Put stores data with the given key
135+
func (p *PowerKv) Put(key, data interface{}) (bool, error) {
136+
keyBytes, err := p.toBytes(key)
137+
if err != nil {
138+
return false, err
139+
}
140+
141+
dataBytes, err := p.toBytes(data)
142+
if err != nil {
143+
return false, err
144+
}
145+
146+
return p.PutBytes(keyBytes, dataBytes)
147+
}
148+
149+
// PutBytes stores byte data with byte key
150+
func (p *PowerKv) PutBytes(key, data []byte) (bool, error) {
151+
url := p.serverURL + "/storeData"
152+
153+
payload := StoreDataRequest{
154+
ProjectID: p.projectID,
155+
Secret: p.secret,
156+
Key: p.toHexString(key),
157+
Value: p.toHexString(data),
158+
}
159+
160+
jsonData, err := json.Marshal(payload)
161+
if err != nil {
162+
return false, NewPowerKvError("NetworkError", fmt.Sprintf("Failed to marshal JSON: %v", err))
163+
}
164+
165+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
166+
defer cancel()
167+
168+
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
169+
if err != nil {
170+
return false, NewPowerKvError("NetworkError", fmt.Sprintf("Failed to create request: %v", err))
171+
}
172+
req.Header.Set("Content-Type", "application/json")
173+
174+
resp, err := p.client.Do(req)
175+
if err != nil {
176+
return false, NewPowerKvError("NetworkError", fmt.Sprintf("Request failed: %v", err))
177+
}
178+
defer resp.Body.Close()
179+
180+
responseBody, err := io.ReadAll(resp.Body)
181+
if err != nil {
182+
return false, NewPowerKvError("NetworkError", fmt.Sprintf("Failed to read response: %v", err))
183+
}
184+
185+
if resp.StatusCode == 200 {
186+
return true, nil
187+
}
188+
189+
// Parse error message
190+
var errorResp ErrorResponse
191+
message := fmt.Sprintf("HTTP %d", resp.StatusCode)
192+
if err := json.Unmarshal(responseBody, &errorResp); err == nil && errorResp.Message != "" {
193+
message = errorResp.Message
194+
} else if len(responseBody) > 0 {
195+
message = fmt.Sprintf("HTTP %d — %s", resp.StatusCode, string(responseBody))
196+
}
197+
198+
return false, NewPowerKvError("ServerError", fmt.Sprintf("storeData failed: %s", message))
199+
}
200+
201+
// GetValue retrieves data for the given key
202+
func (p *PowerKv) GetValue(key interface{}) ([]byte, error) {
203+
keyBytes, err := p.toBytes(key)
204+
if err != nil {
205+
return nil, err
206+
}
207+
208+
return p.GetValueBytes(keyBytes)
209+
}
210+
211+
// GetValueBytes retrieves data for the given byte key
212+
func (p *PowerKv) GetValueBytes(key []byte) ([]byte, error) {
213+
keyHex := p.toHexString(key)
214+
baseURL := p.serverURL + "/getValue"
215+
216+
params := url.Values{}
217+
params.Add("projectId", p.projectID)
218+
params.Add("key", keyHex)
219+
220+
fullURL := baseURL + "?" + params.Encode()
221+
222+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
223+
defer cancel()
224+
225+
req, err := http.NewRequestWithContext(ctx, "GET", fullURL, nil)
226+
if err != nil {
227+
return nil, NewPowerKvError("NetworkError", fmt.Sprintf("Failed to create request: %v", err))
228+
}
229+
230+
resp, err := p.client.Do(req)
231+
if err != nil {
232+
return nil, NewPowerKvError("NetworkError", fmt.Sprintf("Request failed: %v", err))
233+
}
234+
defer resp.Body.Close()
235+
236+
responseBody, err := io.ReadAll(resp.Body)
237+
if err != nil {
238+
return nil, NewPowerKvError("NetworkError", fmt.Sprintf("Failed to read response: %v", err))
239+
}
240+
241+
if resp.StatusCode == 200 {
242+
var response GetValueResponse
243+
if err := json.Unmarshal(responseBody, &response); err != nil {
244+
return nil, NewPowerKvError("ServerError", fmt.Sprintf("Unexpected response shape from /getValue: %s", string(responseBody)))
245+
}
246+
247+
return p.fromHexString(response.Value)
248+
}
249+
250+
// Parse error message
251+
var errorResp ErrorResponse
252+
message := fmt.Sprintf("HTTP %d", resp.StatusCode)
253+
if err := json.Unmarshal(responseBody, &errorResp); err == nil && errorResp.Message != "" {
254+
message = errorResp.Message
255+
} else if len(responseBody) > 0 {
256+
message = fmt.Sprintf("HTTP %d — %s", resp.StatusCode, string(responseBody))
257+
}
258+
259+
return nil, NewPowerKvError("ServerError", fmt.Sprintf("getValue failed: %s", message))
260+
}
261+
262+
// GetStringValue retrieves data as string
263+
func (p *PowerKv) GetStringValue(key interface{}) (string, error) {
264+
data, err := p.GetValue(key)
265+
if err != nil {
266+
return "", err
267+
}
268+
return string(data), nil
269+
}
270+
271+
// GetIntValue retrieves data as int
272+
func (p *PowerKv) GetIntValue(key interface{}) (int, error) {
273+
data, err := p.GetValue(key)
274+
if err != nil {
275+
return 0, err
276+
}
277+
278+
value, err := strconv.Atoi(string(data))
279+
if err != nil {
280+
return 0, NewPowerKvError("ServerError", fmt.Sprintf("Invalid integer: %v", err))
281+
}
282+
return value, nil
283+
}
284+
285+
// GetLongValue retrieves data as int64
286+
func (p *PowerKv) GetLongValue(key interface{}) (int64, error) {
287+
data, err := p.GetValue(key)
288+
if err != nil {
289+
return 0, err
290+
}
291+
292+
value, err := strconv.ParseInt(string(data), 10, 64)
293+
if err != nil {
294+
return 0, NewPowerKvError("ServerError", fmt.Sprintf("Invalid long: %v", err))
295+
}
296+
return value, nil
297+
}
298+
299+
// GetDoubleValue retrieves data as float64
300+
func (p *PowerKv) GetDoubleValue(key interface{}) (float64, error) {
301+
data, err := p.GetValue(key)
302+
if err != nil {
303+
return 0, err
304+
}
305+
306+
value, err := strconv.ParseFloat(string(data), 64)
307+
if err != nil {
308+
return 0, NewPowerKvError("ServerError", fmt.Sprintf("Invalid double: %v", err))
309+
}
310+
return value, nil
311+
}

0 commit comments

Comments
 (0)