feat(http): add vnd.flux content-type support

Fixes #10745
pull/11221/head
Nathaniel Cook 2019-01-17 11:36:05 -07:00
parent 36a7b45908
commit b031e22003
5 changed files with 67 additions and 151 deletions

View File

@ -525,7 +525,7 @@ paths:
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
$ref: "#/components/schemas/Error"
/macros:
get:
tags:
@ -2152,76 +2152,6 @@ paths:
schema:
$ref: "#/components/schemas/Error"
/query:
get:
tags:
- Query
summary: query influx with specified return formatting. The spec and query fields are mutually exclusive.
parameters:
- in: query
name: org
description: specifies the organization of the resource
required: true
schema:
type: string
- in: query
name: query
description: query script to execute.
required: true
schema:
type: string
- in: header
name: Authorization
description: the authorization header should be in the format of `Token <key>`
schema:
type: string
responses:
'200':
description: query results
content:
text/csv:
schema:
type: string
example: >
result,table,_start,_stop,_time,region,host,_value
mean,0,2018-05-08T20:50:00Z,2018-05-08T20:51:00Z,2018-05-08T20:50:00Z,east,A,15.43
mean,0,2018-05-08T20:50:00Z,2018-05-08T20:51:00Z,2018-05-08T20:50:20Z,east,B,59.25
mean,0,2018-05-08T20:50:00Z,2018-05-08T20:51:00Z,2018-05-08T20:50:40Z,east,C,52.62
'400':
description: error processing query
headers:
X-Influx-Error:
description: error string describing the problem
schema:
type: string
X-Influx-Reference:
description: reference code unique to the error type
schema:
type: integer
content:
text/csv:
schema:
type: string
example: >
error,reference
Failed to parse query,897
default:
description: internal server error
headers:
X-Influx-Error:
description: error string describing the problem
schema:
type: string
X-Influx-Reference:
description: reference code unique to the error type
schema:
type: integer
content:
text/csv:
schema:
type: string
example: >
error,reference
Failed to parse query,897
post:
tags:
- Query
@ -2243,6 +2173,7 @@ paths:
type: string
enum:
- application/json
- application/vnd.flux
- in: header
name: Authorization
description: the authorization header should be in the format of `Token <key>`
@ -2259,6 +2190,9 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Query"
application/vnd.flux:
schema:
type: string
responses:
'200':
description: query results
@ -6188,4 +6122,4 @@ components:
protos:
type: array
items:
$ref: "#/components/schemas/Proto"
$ref: "#/components/schemas/Proto"

View File

@ -4,6 +4,8 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"mime"
"net/http"
"regexp"
"strconv"
@ -330,23 +332,32 @@ func QueryRequestFromProxyRequest(req *query.ProxyRequest) (*QueryRequest, error
func decodeQueryRequest(ctx context.Context, r *http.Request, svc platform.OrganizationService) (*QueryRequest, error) {
var req QueryRequest
// TODO(desa): I'm not sure I like this kind of conditional logic, but it feels better than
// introducing another method that does this exact thing.
if r.Method == http.MethodGet {
qp := r.URL.Query()
req.Query = qp.Get("query")
if req.Query == "" {
return nil, errors.New("query param \"query\" is required")
var contentType = "application/json"
if ct := r.Header.Get("Content-Type"); ct != "" {
contentType = ct
}
mt, _, err := mime.ParseMediaType(contentType)
if err != nil {
return nil, err
}
switch mt {
case "application/vnd.flux":
body, err := ioutil.ReadAll(r.Body)
if err != nil {
return nil, err
}
} else {
req.Query = string(body)
case "application/json":
fallthrough
default:
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
}
req = req.WithDefaults()
err := req.Validate()
if err != nil {
if err := req.Validate(); err != nil {
return nil, err
}

View File

@ -48,7 +48,6 @@ func NewFluxHandler() *FluxHandler {
}
h.HandlerFunc("POST", fluxPath, h.handleQuery)
h.HandlerFunc("GET", fluxPath, h.handleQuery)
h.HandlerFunc("POST", "/api/v2/query/ast", h.postFluxAST)
h.HandlerFunc("POST", "/api/v2/query/analyze", h.postQueryAnalyze)
h.HandlerFunc("POST", "/api/v2/query/spec", h.postFluxSpec)

View File

@ -6,6 +6,7 @@ import (
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
"time"
@ -395,6 +396,35 @@ func Test_decodeQueryRequest(t *testing.T) {
},
},
},
{
name: "valid query request with explict content-type",
args: args{
r: func() *http.Request {
r := httptest.NewRequest("POST", "/", bytes.NewBufferString(`{"query": "from()"}`))
r.Header.Set("Content-Type", "application/json")
return r
}(),
svc: &mock.OrganizationService{
FindOrganizationF: func(ctx context.Context, filter platform.OrganizationFilter) (*platform.Organization, error) {
return &platform.Organization{
ID: func() platform.ID { s, _ := platform.IDFromString("deadbeefdeadbeef"); return *s }(),
}, nil
},
},
},
want: &QueryRequest{
Query: "from()",
Type: "flux",
Dialect: QueryDialect{
Delimiter: ",",
DateTimeFormat: "RFC3339",
Header: func(x bool) *bool { return &x }(true),
},
Org: &platform.Organization{
ID: func() platform.ID { s, _ := platform.IDFromString("deadbeefdeadbeef"); return *s }(),
},
},
},
{
name: "error decoding json",
args: args{
@ -465,9 +495,13 @@ func Test_decodeProxyQueryRequest(t *testing.T) {
},
},
{
name: "valid get query request",
name: "valid post vnd.flux query request",
args: args{
r: httptest.NewRequest("GET", "/api/v2/query?query=from(bucket%3A%20%22mybucket%22)&org=myorg", nil),
r: func() *http.Request {
r := httptest.NewRequest("POST", "/api/v2/query?org=myorg", strings.NewReader(`from(bucket: "mybucket")`))
r.Header.Set("Content-Type", "application/vnd.flux")
return r
}(),
svc: &mock.OrganizationService{
FindOrganizationF: func(ctx context.Context, filter platform.OrganizationFilter) (*platform.Organization, error) {
return &platform.Organization{

View File

@ -2638,72 +2638,6 @@ paths:
schema:
$ref: "#/components/schemas/Error"
/query:
get:
tags:
- Query
summary: query influx with specified return formatting. The spec and query fields are mutually exclusive.
parameters:
- $ref: '#/components/parameters/TraceSpan'
- in: query
name: org
description: specifies the organization of the resource
required: true
schema:
type: string
- in: query
name: query
description: query script to execute.
required: true
schema:
type: string
responses:
'200':
description: query results
content:
text/csv:
schema:
type: string
example: >
result,table,_start,_stop,_time,region,host,_value
mean,0,2018-05-08T20:50:00Z,2018-05-08T20:51:00Z,2018-05-08T20:50:00Z,east,A,15.43
mean,0,2018-05-08T20:50:00Z,2018-05-08T20:51:00Z,2018-05-08T20:50:20Z,east,B,59.25
mean,0,2018-05-08T20:50:00Z,2018-05-08T20:51:00Z,2018-05-08T20:50:40Z,east,C,52.62
'400':
description: error processing query
headers:
X-Influx-Error:
description: error string describing the problem
schema:
type: string
X-Influx-Reference:
description: reference code unique to the error type
schema:
type: integer
content:
text/csv:
schema:
type: string
example: >
error,reference
Failed to parse query,897
default:
description: internal server error
headers:
X-Influx-Error:
description: error string describing the problem
schema:
type: string
X-Influx-Reference:
description: reference code unique to the error type
schema:
type: integer
content:
text/csv:
schema:
type: string
example: >
error,reference
Failed to parse query,897
post:
tags:
- Query
@ -2726,6 +2660,7 @@ paths:
type: string
enum:
- application/json
- application/vnd.flux
- in: query
name: org
description: specifies the name of the organization executing the query.
@ -2742,6 +2677,9 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Query"
application/vnd.flux:
schema:
type: string
responses:
'200':
description: query results