| A | B | C | D | E | F | G | H | I | J | |
|---|---|---|---|---|---|---|---|---|---|---|
1 | Lens APIs Response Contract | |||||||||
2 | ||||||||||
3 | Guidelines | |||||||||
4 | Lens REST API Response format is heavily inspired from Google’s Json Style Guide [1] to build RPC based and REST based APIs. Some highlights from guidelines: Reserved JavaScript Keywords shall be avoided in request/response json. If a property is optional or has an empty or null value, consider dropping the property from the JSON, unless there's a strong semantic reason for its existence. | |||||||||
5 | [1] Google Json Style Guide | |||||||||
6 | [2] Reserved JavaScript Keywords | |||||||||
7 | ||||||||||
8 | Response Format of Lens APIs | |||||||||
9 | { "apiVersion": "", "id": "", "data": {}, "error": { "code": <number>, "message": "", "stackTrace": "", "payLoad": {}, "childErrors": [] } } | |||||||||
10 | ||||||||||
11 | Field | Description | Required/Optional | |||||||
12 | apiVersion | Represents the API version used in responding to the request | Required | |||||||
13 | id | A server supplied identifier for the response (regardless of whether the response is a success or an error). This is useful for correlating server logs with individual responses received at a client. | Required | |||||||
14 | data | Container for all the data from a response. A JSON response should contain either a data object or an error object, but not both. If both data and error are present, the error object takes precedence. | Optional | |||||||
15 | error | Indicates that an error has occurred, with details about the error. The error format supports either one or more errors returned from the service. A JSON response should contain either a data object or an error object, but not both. | Optional | |||||||
16 | error.code | Represents the code for this error. | Required | |||||||
17 | error.message | A human readable message providing more details about the error. | Required | |||||||
18 | error.stackTrace | Exception stack trace. | Optional | |||||||
19 | error.payLoad | Container for all the data which can be used to elaborate error response. | Optional | |||||||
20 | error.childErrors | A collection of errors which have lead to the parent error. In a multithreaded application, main thread might collect errors from all background threads and return all of them as child errors of parent error. Every childError instance shall itself have the same structure as the parent error. | Optional | |||||||
21 | ||||||||||
22 | How to identify whether a request has succeeded or has encountered an error ? | |||||||||
23 | There are two ways to identify whether a request has succeeded or has encountered an error : (1) All 2XX class of Http Status Codes indicates success and remaining all codes indicates that request has encountered an error. (2) Absence of error property at the top level in response body indicates success and presence of the same property indicates that request has encountered an error. (1) and (2) will always be in sync. E.g.: It would never happen that Http Status code is 2XX and response body has error property in it. | |||||||||
24 | ||||||||||
25 | API Internal Name | API URI HTTP Method | Result | Current HTTP Status Code | New HTTP Status Code | Current Response | New Response | New Response (XML) | ||
26 | openSession | /session POST parameters passed: username, passwd, sessionconf, database | Success: Session Created | 200 | 201 | { "secretId": "...", "publicId": "..." } | { "apiVersion": "...", "id": "...", "data": { "secretId": "...", "publicId": "..." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <data> <publicId>...</publicId> <secretId>...</secretId> </data> <id>...</id> </root> | ||
27 | Error: Null, empty or blank Username | 400 | 400 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": TBD, "message": "No username provided" } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>TBD</code> <message>No username provided</message> </error> <id>...</id> </root> | ||||
28 | Error: Authentication Failed | 401 | 401 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": TBD, "message": "Invalid Username or password." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>TBD</code> <message>Invalid Username or password.</message> </error> <id>...</id> </root> | ||||
29 | Error: HiveException/LensException | 500 | 500 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": 1001, "message": "Internal Server Error." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>1001</code> <message>Internal Server Error.</message> </error> <id>...</id> </root> | ||||
30 | Error: Provided database name does not exist. | 404 | 400 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": TBD, "message": "Could not set current database for the session. Provided database name does not exist." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>TBD</code> <message>Could not set current database for the session. Provided database name does not exist.</message> </error> <id>...</id> </root> | ||||
31 | getLatestDate | /metastore/cubes/{cubeName}/latestdate GET | Success: Returns LatestDate | 200 | 200 | { "date": "..." } | { "apiVersion": "...", "id": "...", "data": { "date": "..." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <data> <date>...</date> </data> <id>...</id> </root> | ||
32 | Error: Null Session id | 400 | 400 | Empty String | { "type": "errorResponse", "apiVersion": "...", "id": "...", "error": { "code": 2001, "message": "Session id not provided. Please provide a session id." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>2001</code> <message>Session id not provided. Please provide a session id.</message> </error> <id>...</id> </root> | ||||
33 | Error: Invalid Cube | 400 | 400 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": TBD, "message": "Provided cube name is not valid." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>TBD</code> <message>Provided cube name is not valid.</message> </error> <id>...</id> </root> | ||||
34 | Error: Invalid Fact in Cube Definition | 404 | 500 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": 1001, "message": "Internal Server Error." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>1001</code> <message>Internal Server Error.</message> </error> <id>...</id> </root> | ||||
35 | Error: HiveException/LensException | 500 | 500 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": 1001, "message": "Internal Server Error." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>1001</code> <message>Internal Server Error.</message> </error> <id>...</id> </root> | ||||
36 | cancelQuery | /queryapi/queries/{queryHandle} DELETE Remarks: Current response can be improved for this API. In current state, there are two ways to identify failure. A return false and LensException both represents failure in cancellation in internal code and REST layer responds differently for both of them to client. We can have only one uniform way to represent failure. Underlying LensDriver interface can also be improved with respect to cancel query. It also has two ways to represent cancellation failure. We can have only one way for representing failure i.e. Exception throughout the code. public interface LensDriver { boolean cancelQuery(QueryHandle handle) throws LensException; } | Success | 200 | 200 | { "message" : "...", "status" : "SUCCEEDED" } | { "apiVersion": "...", "id": "...", } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <id>...</id> </root> | ||
37 | Error: Null Session id | 400 | 400 | Empty String | { "type": "errorResponse", "apiVersion": "...", "id": "...", "error": { "code": 2001, "message": "Session id not provided. Please provide a session id." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>2001</code> <message>Session id not provided. Please provide a session id.</message> </error> <id>...</id> </root> | ||||
38 | Error: Invalid query handle | 400 | 400 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": TBD, "message": "Query does not exist." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>TBD</code> <message>Query does not exist.</message> </error> <id>...</id> </root> | ||||
39 | Error: Driver or internal lens server code returned false | 200 | 500 | { "message" : "...", "status" : "FAILED" } | { "apiVersion": "...", "id": "...", "error": { "code": 1001, "message": "Internal Server Error." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>1001</code> <message>Internal Server Error.</message> </error> <id>...</id> </root> | ||||
40 | Error: LensException | 500 | 500 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": 1001, "message": "Internal Server Error." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>1001</code> <message>Internal Server Error.</message> </error> <id>...</id> </root> | ||||
41 | Error: Query Execution has already finished | 200 | 500 | { "message" : "...", "status" : "FAILED" } | { "apiVersion": "...", "id": "...", "error": { "code": TBD, "message": "Query execution has already finished. It cannot be cancelled." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>TBD</code> <message>Query execution has already finished. It cannot be cancelled.</message> </error> <id>...</id> </root> | ||||
42 | Error: Query does not exist. | 404 | 404 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": TBD, "message": "Query does not exists." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>TBD</code> <message>Query does not exists.</message> </error> <id>...</id> </root> | ||||
43 | query ESTIMATE | /queries POST parameters passed: sessionid, query, operation#ESTIMATE, conf, timeoutmillis, queryname | Success: Query Estimation Successful | 200 | 200 | { "error" : false, "cost" : { "estimatedExecTimeMillis" : ..., "estimatedResourceUsage" : ... }, "errorMsg" : "..." } | { "apiVersion": "...", "id": "...", "data": { "estimatedExecTimeMillis": "...", "estimatedResourceUsage": "..." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <data> <estimatedExecTimeMillis>...</estimatedExecTimeMillis> <estimatedResourceUsage>...</estimatedResourceUsage> </data> <id>...</id> </root> | ||
44 | Error: Null Session id | 400 | 400 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": 2001, "message": "Session id not provided. Please provide a session id.", "stackTrace": "..." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>2001</code> <message>Session id not provided. Please provide a session id.</message> <stackTrace>...<stackTrace> </error> <id>...</id> </root> | ||||
45 | Error: Submitted query is null or empty or blank | 400 | 400 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": 2002, "message": "Query is not provided, or it is empty or blank. Please provide a valid query.", "stackTrace": "..." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>2002</code> <message>Query is not provided, or it is empty or blank. Please provide a valid query.</message> <stackTrace>...<stackTrace> </error> <id>...</id> </root> | ||||
46 | Error: Invalid Operation Submitted | 400 | 400 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": "2003", "message": "Provided Operation is not supported. Supported Operations are: [estimate, execute, explain, execute_with_timeout]", "stackTrace": "...", "payload": { "type": "supportedQuerySubmitOperations", "supportedOperations": [ "estimate", "execute", "explain", "execute_with_timeout" ] } } } | <?xml version="1.0" encoding="UTF-8"?> <lensResponse> <apiVersion>...</apiVersion> <id>...</id> <error> <code>2003</code> <message>Provided Operation is not supported. Supported Operations are: [estimate, execute, explain, execute_with_timeout]</message> <stackTrace>...</stackTrace> <payload xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="supportedQuerySubmitOperations"> <supportedOperations> <operation>estimate</operation> <operation>execute</operation> <operation>explain</operation> <operation>execute_with_timeout</operation> </supportedOperations> </payload> </error> </lensResponse> | ||||
47 | Error: LensException | 200 | 500 | { "error" : true, "cost" : null, "errorMsg" : "..." } | { "apiVersion": "...", "id": "...", "error": { "code": 1001, "message": "Internal Server Error.", "stackTrace":"..." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>1001</code> <message>Internal Server Error.</message> <stackTrace>...<stackTrace> </error> <id>...</id> </root> | ||||
48 | Error: Exception in Parsing Query | 200 | 400 | { "error" : true, "cost" : null, "errorMsg" : "..." } | { "apiVersion": "...", "id": "...", "error": { "code": 3001, "message": "Syntax error: [syntax error msg]", "stackTrace": "..." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>3001</code> <message>Syntax error: [syntax error msg]</message> <stackTrace>...<stackTrace> </error> <id>...</id> </root> | ||||
49 | Error: Fields cannot be queried together. | 200 | 400 | { "error" : true, "cost" : null, "errorMsg" : "..." } | { "apiVersion": "...", "id": "...", "error": { "code": TBD, "message": "Selected fields cannot be queried together. Please remove some fields and try again." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>TBD</code> <message>Selected fields cannot be queried together. Please remove some fields and try again.</message> </error> <id>...</id> </root> | ||||
50 | Error: Some columns cannot be queried in selected time range. | 200 | 400 | { "error" : true, "cost" : null, "errorMsg" : "..." } | { "apiVersion": "...", "id": "...", "error": { "code": "3002", "message": "{ColumnName} can only be queried after {StartTime} [and before {EndTime}]. Please adjust the selected time range accordingly.", "stackTrace": "...", "payload": { "type": "colUnAvailableInTimeRange", "columnName": "...", "availableFromInMillisSinceEpoch": "...", "availableTillInMillisSinceEpoch": "..." } } } | <?xml version="1.0" encoding="UTF-8"?> <lensResponse> <apiVersion>...</apiVersion> <id>...</id> <error> <code>3002</code> <message>{ColumnName} can only be queried after {StartTime} [and before {EndTime}]. Please adjust the selected time range accordingly.</message> <stackTrace>...</stackTrace> <payload xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="colUnAvailableInTimeRange"> <columnName>...</columnName> <availableFromInMillisSinceEpoch>...</availableFromInMillisSinceEpoch> <availableTillInMillisSinceEpoch>...</availableTillInMillisSinceEpoch> </payload> </error> </lensResponse> | ||||
51 | query EXECUTE | /queries POST parameters passed: sessionid, query, operation#EXECUTE, conf, timeoutmillis, queryname | Success: Query accepted for asynchronous execution. | 200 | 202 | { "handleId": "..." } | { "apiVersion": "...", "id": "...", "data": { "handleId": "..." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <data> <queryHandle> <handleId>...</handleId> </queryHandle> </data> <id>...</id> </root> | ||
52 | Error: Null Session id | 400 | 401 | Empty String | { "type": "errorResponse", "apiVersion": "...", "id": "...", "error": { "code": 2001, "message": "Session id not provided. Please provide a session id." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>2001</code> <message>Session id not provided. Please provide a session id.</message> </error> <id>...</id> </root> | ||||
53 | Error: Submitted query is null or empty or blank | 400 | 400 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": 2002, "message": "Query is not provided, or it is empty or blank. Please provide a valid query." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>2002</code> <message>Query is not provided, or it is empty or blank. Please provide a valid query.</message> </error> <id>...</id> </root> | ||||
54 | Error: Invalid Operation Submitted | 400 | 400 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": "2003", "message": "Provided Operation is not supported. Supported Operations are: [estimate, execute, explain, execute_with_timeout]", "stackTrace": "...", "payload": { "type": "supportedQuerySubmitOperations", "supportedOperations": [ "estimate", "execute", "explain", "execute_with_timeout" ] } } } | <?xml version="1.0" encoding="UTF-8"?> <lensResponse> <error> <code>2003</code> <message>Provided Operation is not supported. Supported Operations are: [estimate, execute, explain, execute_with_timeout]</message> <stackTrace /> <payload xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="supportedQuerySubmitOperations"> <supportedOperations> <operation>estimate</operation> <operation>execute</operation> <operation>explain</operation> <operation>execute_with_timeout</operation> </supportedOperations> </payload> </error> </lensResponse> | ||||
55 | getStatus (name shall be refactored to getQuery as the API returns the entire lensQueryResource instead of just returning status) | /queries/{queryHandle} GET parameters passed: sessionId | Success: Query retrieved successfully | 200 | 200 | { "driverOpHandle": "...", "queryHandle": { "handleId": "..." }, "closedTime": 999, "driverQuery": "...", "selectedDriverClassName": "...", "status": { "progressMessage": "...", "progress": 999, "isResultSetAvailable": false, "statusMessage": "...", "errorMessage": "...", "status": "NEW" }, "launchTime": 999, "resultSetPath": "...", "queryName": "...", "driverFinishTime": 999, "driverStartTime": 999, "isPersistent": false, "submissionTime": 999, "queryConf": { "properties": { "key": "value" } }, "finishTime": 999, "submittedUser": "...", "priority": "VERY_HIGH", "userQuery": "..." } | { "apiVersion": "...", "id": "...", "data": { "query": { "driverOpHandle": "...", "queryHandle": { "handleId": "..." }, "closedTime": 999, "driverQuery": "...", "selectedDriverClassName": "...", "status": { "progressMessage": "...", "progress": 999, "isResultSetAvailable": false, "statusMessage": "...", "errorMessage": "...", "status": "NEW" }, "launchTime": 999, "resultSetPath": "...", "queryName": "...", "driverFinishTime": 999, "driverStartTime": 999, "isPersistent": false, "submissionTime": 999, "queryConf": { "properties": { "key": "value" } }, "finishTime": 999, "submittedUser": "...", "priority": "VERY_HIGH", "userQuery": "..." } } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <data> <query> <closedTime>999</closedTime> <driverFinishTime>999</driverFinishTime> <driverOpHandle>...</driverOpHandle> <driverQuery>...</driverQuery> <driverStartTime>999</driverStartTime> <finishTime>999</finishTime> <isPersistent>false</isPersistent> <launchTime>999</launchTime> <priority>VERY_HIGH</priority> <queryConf> <properties> <key>value</key> </properties> </queryConf> <queryHandle> <handleId>...</handleId> </queryHandle> <queryName>...</queryName> <resultSetPath>...</resultSetPath> <selectedDriverClassName>...</selectedDriverClassName> <status> <errorMessage>...</errorMessage> <isResultSetAvailable>false</isResultSetAvailable> <progress>999</progress> <progressMessage>...</progressMessage> <status>NEW</status> <statusMessage>...</statusMessage> </status> <submissionTime>999</submissionTime> <submittedUser>...</submittedUser> <userQuery>...</userQuery> </query> </data> <id>...</id> </root> | ||
56 | Error: Null Session id | 400 | 400 | Empty String | { "type": "errorResponse", "apiVersion": "...", "id": "...", "error": { "code": 2001, "message": "Session id not provided. Please provide a session id." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>2001</code> <message>Session id not provided. Please provide a session id.</message> </error> <id>...</id> </root> | ||||
57 | Error: Query does not exist. | 404 | 404 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": TBD, "message": "Query does not exists." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>TBD</code> <message>Query does not exists.</message> </error> <id>...</id> </root> | ||||
58 | setDatabase | /metastore/databases/current PUT parameter passed: session id, dbName Remarks: I might be wrong but in my opinion this API does not belong to /metastore and it shall not use PUT HTTP method. This API is updating current db of a session resource referenced by session id, so it shall be a part of /session. Also it is doing a partial update of a resource. For partial update we shall use POST or PATCH. In PUT a client shall pass a complete resource entity to be replaced by server. We could have had an API as follows: /session/{sessionId} POST | PATCH Request Body: { "data": { "operations": [ { "op": "replace", "path": "/currentDatabase", "value": "<dbName>" } ] } } | Success: current database set for the session | 200 | 200 | { "message": "...", "status": "SUCCEEDED" } | { "apiVersion": "...", "id": "...", } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <id>...</id> </root> | ||
59 | Error: Null Session id | 400 | 400 | Empty String | { "type": "errorResponse", "apiVersion": "...", "id": "...", "error": { "code": 2001, "message": "Session id not provided. Please provide a session id." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>2001</code> <message>Session id not provided. Please provide a session id.</message> </error> <id>...</id> </root> | ||||
60 | Error: Provided database name does not exist. | 404 | 400 | Empty String | { "apiVersion": "...", "id": "...", "error": { "code": TBD, "message": "Could not set current database for the session. Provided database name does not exist." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>TBD</code> <message>Could not set current database for the session. Provided database name does not exist.</message> </error> <id>...</id> </root> | ||||
61 | Error: HiveException/LensException | 200 | 500 | { "message": "...", "status": "FAILED" } | { "apiVersion": "...", "id": "...", "error": { "code": 1001, "message": "Internal Server Error." } } | <?xml version="1.0" encoding="UTF-8"?> <root> <apiVersion>...</apiVersion> <error> <code>1001</code> <message>Internal Server Error.</message> </error> <id>...</id> </root> | ||||
62 | ||||||||||