/
search.go
146 lines (125 loc) · 3.79 KB
/
search.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
138
139
140
141
142
143
144
145
146
package rets
import (
"bytes"
"encoding/xml"
"io"
"net/http"
"net/url"
"strconv"
"context"
)
// Count is the type of serverside count response that should be included
const (
// CountNone dont include a count
CountNone = iota
// CountIncluded include a count after the data
CountIncluded = iota
// CountOnly returns only the count
CountOnly = iota
)
const (
// StandardNames requests normalized naming
StandardNames = iota
// SystemNames (the default) requests whatever field names are used by the server
SystemNames = iota
)
// SearchParams is the configuration for creating a SearchReqeust
type SearchParams struct {
SearchType, // Property
Class string // Residential
Format, // 7.4.2 COMPACT | COMPACT-DECODED | STANDARD-XML | STANDARD-XML:dtd-version
Select string
// Payload should not be used with the format,select pair
Payload string //The Client may request a specific XML format for the return set.
// Query should be in the format specified by QueryType
Query,
QueryType string // DMQL2 is the standard option
// RestrictedIndicator is the symbol to be used for fields that are blanked serverside (e.g. ####)
RestrictedIndicator string
StandardNames int // (0|1|)
Count int // (|0|1|2)
Limit int // <0 => "NONE"
// Offset is properly started at 1, or left blank and assumed to have been 1
Offset int
}
// SearchRequest holds the information needed to send a RETS request
type SearchRequest struct {
URL,
HTTPMethod string
HTTPFormEncodedValues bool // POST style http params
SearchParams
}
// PrepSearchRequest creates an http.Request from a SearchRequest
func PrepSearchRequest(r SearchRequest) (*http.Request, error) {
url, err := url.Parse(r.URL)
if err != nil {
return nil, err
}
values := url.Query()
// required
values.Add("Class", r.Class)
values.Add("SearchType", r.SearchType)
// optional
optionalString := OptionalStringValue(values)
optionalString("Format", r.Format)
optionalString("Select", r.Select)
optionalString("Payload", r.Payload)
optionalString("Query", r.Query)
optionalString("QueryType", r.QueryType)
optionalString("RestrictedIndicator", r.RestrictedIndicator)
optionalInt := OptionalIntValue(values)
if r.Count > 0 {
optionalInt("Count", r.Count)
}
if r.Offset > 0 {
optionalInt("Offset", r.Offset)
}
if r.StandardNames > 0 {
optionalInt("StandardNames", r.StandardNames)
}
// limit is unique in that it can send a value of "NONE"
switch {
case r.Limit > 0:
optionalInt("Limit", r.Limit)
case r.Limit < 0:
values.Add("Limit", "NONE")
}
method := "GET"
if r.HTTPMethod != "" {
method = r.HTTPMethod
}
// http POST style params
if r.HTTPFormEncodedValues {
req, err := http.NewRequest(method, url.String(), bytes.NewBufferString(values.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
return req, err
}
// the standard query string style params here
url.RawQuery = values.Encode()
return http.NewRequest(method, url.String(), nil)
}
// SearchResponse returns the raw stream from the RETS server response
func SearchResponse(ctx context.Context, requester Requester, r SearchRequest) (*http.Response, error) {
req, err := PrepSearchRequest(r)
if err != nil {
return nil, err
}
return requester(ctx, req)
}
// SearchStream wraps the body with proper content decoding given the content type or char encoding
func SearchStream(resp *http.Response, err error) (io.ReadCloser, error) {
if err != nil {
return nil, err
}
return DefaultReEncodeReader(resp.Body, resp.Header.Get(ContentType)), nil
}
// countTag wraps an xml.StartElement to extract response Count information
type countTag xml.StartElement
// Parse ...
func (ct countTag) Parse() (int, error) {
code, err := strconv.ParseInt(ct.Attr[0].Value, 10, 64)
if err != nil {
return -1, err
}
return int(code), nil
}