1package storage
2
3// Copyright 2017 Microsoft Corporation
4//
5//  Licensed under the Apache License, Version 2.0 (the "License");
6//  you may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at
8//
9//      http://www.apache.org/licenses/LICENSE-2.0
10//
11//  Unless required by applicable law or agreed to in writing, software
12//  distributed under the License is distributed on an "AS IS" BASIS,
13//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14//  See the License for the specific language governing permissions and
15//  limitations under the License.
16
17import (
18	"encoding/json"
19	"fmt"
20	"io/ioutil"
21	"net/http"
22	"net/url"
23	"strconv"
24)
25
26const (
27	headerAccept          = "Accept"
28	headerEtag            = "Etag"
29	headerPrefer          = "Prefer"
30	headerXmsContinuation = "x-ms-Continuation-NextTableName"
31)
32
33// TableServiceClient contains operations for Microsoft Azure Table Storage
34// Service.
35type TableServiceClient struct {
36	client Client
37	auth   authentication
38}
39
40// TableOptions includes options for some table operations
41type TableOptions struct {
42	RequestID string
43}
44
45func (options *TableOptions) addToHeaders(h map[string]string) map[string]string {
46	if options != nil {
47		h = addToHeaders(h, "x-ms-client-request-id", options.RequestID)
48	}
49	return h
50}
51
52// QueryNextLink includes information for getting the next page of
53// results in query operations
54type QueryNextLink struct {
55	NextLink *string
56	ml       MetadataLevel
57}
58
59// GetServiceProperties gets the properties of your storage account's table service.
60// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/get-table-service-properties
61func (t *TableServiceClient) GetServiceProperties() (*ServiceProperties, error) {
62	return t.client.getServiceProperties(tableServiceName, t.auth)
63}
64
65// SetServiceProperties sets the properties of your storage account's table service.
66// See: https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/set-table-service-properties
67func (t *TableServiceClient) SetServiceProperties(props ServiceProperties) error {
68	return t.client.setServiceProperties(props, tableServiceName, t.auth)
69}
70
71// GetTableReference returns a Table object for the specified table name.
72func (t *TableServiceClient) GetTableReference(name string) *Table {
73	return &Table{
74		tsc:  t,
75		Name: name,
76	}
77}
78
79// QueryTablesOptions includes options for some table operations
80type QueryTablesOptions struct {
81	Top       uint
82	Filter    string
83	RequestID string
84}
85
86func (options *QueryTablesOptions) getParameters() (url.Values, map[string]string) {
87	query := url.Values{}
88	headers := map[string]string{}
89	if options != nil {
90		if options.Top > 0 {
91			query.Add(OdataTop, strconv.FormatUint(uint64(options.Top), 10))
92		}
93		if options.Filter != "" {
94			query.Add(OdataFilter, options.Filter)
95		}
96		headers = addToHeaders(headers, "x-ms-client-request-id", options.RequestID)
97	}
98	return query, headers
99}
100
101// QueryTables returns the tables in the storage account.
102// You can use query options defined by the OData Protocol specification.
103//
104// See https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/query-tables
105func (t *TableServiceClient) QueryTables(ml MetadataLevel, options *QueryTablesOptions) (*TableQueryResult, error) {
106	query, headers := options.getParameters()
107	uri := t.client.getEndpoint(tableServiceName, tablesURIPath, query)
108	return t.queryTables(uri, headers, ml)
109}
110
111// NextResults returns the next page of results
112// from a QueryTables or a NextResults operation.
113//
114// See https://docs.microsoft.com/rest/api/storageservices/fileservices/query-tables
115// See https://docs.microsoft.com/rest/api/storageservices/fileservices/query-timeout-and-pagination
116func (tqr *TableQueryResult) NextResults(options *TableOptions) (*TableQueryResult, error) {
117	if tqr == nil {
118		return nil, errNilPreviousResult
119	}
120	if tqr.NextLink == nil {
121		return nil, errNilNextLink
122	}
123	headers := options.addToHeaders(map[string]string{})
124
125	return tqr.tsc.queryTables(*tqr.NextLink, headers, tqr.ml)
126}
127
128// TableQueryResult contains the response from
129// QueryTables and QueryTablesNextResults functions.
130type TableQueryResult struct {
131	OdataMetadata string  `json:"odata.metadata"`
132	Tables        []Table `json:"value"`
133	QueryNextLink
134	tsc *TableServiceClient
135}
136
137func (t *TableServiceClient) queryTables(uri string, headers map[string]string, ml MetadataLevel) (*TableQueryResult, error) {
138	if ml == EmptyPayload {
139		return nil, errEmptyPayload
140	}
141	headers = mergeHeaders(headers, t.client.getStandardHeaders())
142	headers[headerAccept] = string(ml)
143
144	resp, err := t.client.exec(http.MethodGet, uri, headers, nil, t.auth)
145	if err != nil {
146		return nil, err
147	}
148	defer resp.Body.Close()
149
150	if err := checkRespCode(resp, []int{http.StatusOK}); err != nil {
151		return nil, err
152	}
153
154	respBody, err := ioutil.ReadAll(resp.Body)
155	if err != nil {
156		return nil, err
157	}
158	var out TableQueryResult
159	err = json.Unmarshal(respBody, &out)
160	if err != nil {
161		return nil, err
162	}
163
164	for i := range out.Tables {
165		out.Tables[i].tsc = t
166	}
167	out.tsc = t
168
169	nextLink := resp.Header.Get(http.CanonicalHeaderKey(headerXmsContinuation))
170	if nextLink == "" {
171		out.NextLink = nil
172	} else {
173		originalURI, err := url.Parse(uri)
174		if err != nil {
175			return nil, err
176		}
177		v := originalURI.Query()
178		v.Set(nextTableQueryParameter, nextLink)
179		newURI := t.client.getEndpoint(tableServiceName, tablesURIPath, v)
180		out.NextLink = &newURI
181		out.ml = ml
182	}
183
184	return &out, nil
185}
186
187func addBodyRelatedHeaders(h map[string]string, length int) map[string]string {
188	h[headerContentType] = "application/json"
189	h[headerContentLength] = fmt.Sprintf("%v", length)
190	h[headerAcceptCharset] = "UTF-8"
191	return h
192}
193
194func addReturnContentHeaders(h map[string]string, ml MetadataLevel) map[string]string {
195	if ml != EmptyPayload {
196		h[headerPrefer] = "return-content"
197		h[headerAccept] = string(ml)
198	} else {
199		h[headerPrefer] = "return-no-content"
200		// From API version 2015-12-11 onwards, Accept header is required
201		h[headerAccept] = string(NoMetadata)
202	}
203	return h
204}
205