-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgofmcon.go
137 lines (114 loc) · 3.41 KB
/
gofmcon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package gofmcon
import (
"context"
"encoding/xml"
"fmt"
"io"
"net/http"
"net/url"
"errors"
)
const (
fmiPath = "fmi/xml/fmresultset.xml"
// FMDBNames adds –dbnames (Database names) query command
FMDBNames = "-dbnames"
)
// FMConnector includes all the information about FM database to be able to connect to that
type FMConnector struct {
Host string
Port string
Username string
Password string
Client *http.Client
Debug bool
}
// NewFMConnector creates new FMConnector object
func NewFMConnector(host string, port string, username string, password string) *FMConnector {
newConn := &FMConnector{
Host: host,
Port: port,
Username: username,
Password: password,
Client: http.DefaultClient,
}
return newConn
}
// SetDebug sets debug level of logger to Debug.
// DON'T use it in production. Your record information can leak to the logs
func (fmc *FMConnector) SetDebug(v bool) {
fmc.Debug = v
}
// Ping sends a simple request querying all available databases
// in order to check connection and credentials
func (fmc *FMConnector) Ping(ctx context.Context) error {
var newURL = &url.URL{}
newURL.Scheme = "http"
newURL.Host = fmc.Host
newURL.Path = fmiPath
newURL.RawQuery = newURL.Query().Encode() + "&" + FMDBNames
request, err := http.NewRequestWithContext(ctx, "GET", newURL.String(), nil)
if err != nil {
return fmt.Errorf("gofmcon.Ping: error create request: %w", err)
}
request.SetBasicAuth(fmc.Username, fmc.Password)
request.Header.Set("User-Agent", "Golang FileMaker Connector")
client := &http.Client{}
res, err := client.Do(request)
if err != nil {
return err
}
defer res.Body.Close()
if res.StatusCode != 200 {
return fmt.Errorf("gofmcon.Ping: FileMaker server unreachable, status code: %d", res.StatusCode)
}
return nil
}
// Query fetches FMResultset from FileMaker server depending on FMQuery
// given to it
func (fmc *FMConnector) Query(ctx context.Context, q *FMQuery) (FMResultset, error) {
resultSet := FMResultset{}
queryURL := fmc.makeURL(q)
request, err := http.NewRequestWithContext(ctx, "GET", queryURL, nil)
if err != nil {
return resultSet, fmt.Errorf("gofmcon.Query: error create request: %w", err)
}
request.Header.Set("User-Agent", "Golang FileMaker Connector")
request.SetBasicAuth(fmc.Username, fmc.Password)
if fmc.Client == nil {
fmc.Client = http.DefaultClient
}
res, err := fmc.Client.Do(request)
if err != nil {
return resultSet, fmt.Errorf("gofmcon.Query: error http request: %w", err)
}
defer res.Body.Close()
b, err := io.ReadAll(res.Body)
if err != nil {
return resultSet, fmt.Errorf("gofmcon.Query: error read response body: %w", err)
}
if res.StatusCode == 401 {
return resultSet, errors.New("gofmcon.Query: unauthorized")
}
if res.StatusCode < 200 || res.StatusCode > 299 {
return resultSet, fmt.Errorf("gofmcon.Query: unknown error with status code: %d, %s", res.StatusCode, string(b))
}
err = xml.Unmarshal(b, &resultSet)
if err != nil {
return resultSet, fmt.Errorf("gofmcon.Query: error unmarshal xml: %w", err)
}
if resultSet.HasError() {
return resultSet, errors.New(resultSet.FMError.String())
}
resultSet.prepareRecords()
return resultSet, nil
}
func (fmc *FMConnector) makeURL(q *FMQuery) string {
var newURL = &url.URL{}
newURL.Scheme = "http"
newURL.Host = fmc.Host
if fmc.Port != "" {
newURL.Host += ":" + fmc.Port
}
newURL.Path = fmiPath
return newURL.String() + "?" + q.QueryString()
}