diff --git a/.gitignore b/.gitignore index 03e1cb7..1886b15 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ +# Directories and patterns to ignore. EIFGENs +.git .svn/ *.swp *~ diff --git a/README.md b/README.md new file mode 100644 index 0000000..44a41e5 --- /dev/null +++ b/README.md @@ -0,0 +1,456 @@ +## **Firebase REST API. Eiffel Client** +Based on Firebase REST API: https://www.firebase.com/docs/rest/api/ + +---------- + +### **Documentation for REST API Development** +The aim of this project is to develop as many features as possible from the Firebase REST API documentation. However, in the initial meeting, streaming has been discussed as an optional feature if time permits for it to be developed. This README.md will be divided into 5 sections: + + 1. Outline of all Features - Outlines all features list in the documentation in 2015. + 2. Developed Features - Description of the feature and how to use it. + 3. Partially Developed Features - Description of the feature, reasons for its partial-development and how to use it. + 4. Undeveloped Features - Description of the feature and reasons for why its lack of development. + 5. Future Development - Ideas of what a future release could include. + +---------- +#### **Outline of all Features** + - API Usage + - Get (Already Developed) + - Put (Already Developed) + - Post (Already Developed) + - Patch (Already Developed) + - Delete (Already Developed) + - Query + - Auth (Already Developed) + - Shallow (Developed) + - Print Format (Developed) + - Callback (Undeveloped) + - Format (Developed) + - Download (Developed) + - OrderBy (Developed) + - LimitToFirst, LimitToLast, StartAt, EndAt, EqualTo (Developed) + - Stream (Undeveloped) + - Priorities (Partially-Developed) + - Server Values (Undeveloped) + - Security and Rules (Developed) + +---------- +#### **Developed Features** +The following features have been fully developed. All features have been implemented in the firebase_api class. + +---------- +**Get - API Usage** +**Link:** +https://www.firebase.com/docs/rest/api/#section-get + +**Description:** +Data from our Firebase database can be read by issuing an HTTP GET request to an endpoint. A successful request will be indicated by a 200 OK HTTP status code. + +**Method:** + + get (a_path: detachable READABLE_STRING_8): detachable RESPONSE + +**Example:** + + get ("/keyA") + + +---------- + +**Put - API Usage** +**Link:** +https://www.firebase.com/docs/rest/api/#section-put + +**Description:** +We can write data with a PUT request. A successful request will be indicated by a 200 OK HTTP status code. + +**Method:** + + put (a_path: detachable READABLE_STRING_8; a_value: READABLE_STRING_8): detachable RESPONSE + +**Example:** + + put ("/keyA", "{%"keyAA%": %"valueAA%"}") + +---------- +**Post - API Usage** +**Link:** +https://www.firebase.com/docs/rest/api/#section-post + +**Description:** +To accomplish the equivalent of the JavaScript push() method, we can issue a POST request. A successful request will be indicated by a 200 OK HTTP status code. + +**Method:** + + post (a_path: detachable READABLE_STRING_8; a_value: READABLE_STRING_8): detachable RESPONSE + +**Example:** + + post ("/keyA", "{%"keyAA%": %"valueAA%"}") + +---------- +**Patch - API Usage** +**Link:** +https://www.firebase.com/docs/rest/api/#section-patch + +**Description:** +We can update specific children at a location without overwriting existing data using a PATCH request. Named children in the data being written with PATCH will be overwritten, but omitted children will not be deleted. This is equivalent to the JavaScript update() function. A successful request will be indicated by a 200 OK HTTP status code. + +**Method:** + + patch (a_path: detachable READABLE_STRING_8; a_value: READABLE_STRING_8): detachable RESPONSE + +**Example:** + + patch ("/keyA", "{%"keyAA%": %"valueBB%"}") + +---------- +**Delete - API Usage** +**Link:** +https://www.firebase.com/docs/rest/api/#section-delete + +**Description:** +We can delete data with a DELETE request. A successful DELETE request will be indicated by a 200 OK HTTP status code with a response containing JSON null. + +**Method:** +https://www.firebase.com/docs/rest/api/#section-delete + +**Example:** + + delete ("/keyA") + +---------- +**Query - Auth** +**Link:** +https://www.firebase.com/docs/rest/api/#section-param-auth + +**Description:** +Supported by all request types. Authenticates this request to allow access to data protected by Security and Firebase Rules. The argument can either be your Firebase app's secret or an authentication token. + +**Method:** + + make_with_auth (a_base_uri: READABLE_STRING_8; a_auth: READABLE_STRING_8) + +**Example:** + + make_with_auth("https://fiery-fire-4173.firebaseio.com", "33ZXLlDjqpdLzb6DiAi17KkAA6qvzkF40M3MKxWL") + +**Note:** +If the user does not require an authentication token to communicate with the database, the following feature should be used. + + make (a_base_uri: READABLE_STRING_8) + +---------- +**Query - Shallow** +**Link:** +https://www.firebase.com/docs/rest/api/#section-param-shallow + +**Description:** +This is an advanced feature, designed to help you work with large datasets without needing to download everything. Set this to true to limit the depth of the data returned at a location. If the data at the location is a JSON primitive (string, number or boolean), its value will simply be returned. If the data snapshot at the location is a JSON object, the values for each key will be truncated to true. + +**Method:** + + set_shallow (option: BOOLEAN) + +**Example:** +By default, the shallow query is set to `False`. To set the shallow query value to `True`: + + set_shallow (True) + +To set shallow back to `False`: + + set_shallow (False) + +**Note:** +According to the documentation, the shallow query cannot be mixed with other query parameters. To reflect this property, a postcondition was developed for the `new_uri` method: + + is_shallow_valid: is_shallow = True implies (query_count = 1) + +However when testing, it was discovered that shallow could be combined with at least 2 queries (auth and print format). As it is possible that other queries can be combined with the shallow query, the postcondition was removed. + +In addition, a helper method was implemented to clear all query values simultaneously. If too many values are set, developers can use this method instead of unsetting each individual value. + + clear_all_query_values + +---------- +**Query - Print Format** +**Link:** +https://www.firebase.com/docs/rest/api/#section-param-print + +**Description:** +Formats the data returned in the response from the server. + +**Method:** + + set_print_format (option: detachable READABLE_STRING_8) + +**Example:** +By default, the print format query is not set. To set the print format query value to `pretty`: + + set_print_format ("pretty") + +To set the value to `silent`: + + set_print_format ("silent") + +To unset the value: + + set_print_format (Void) + +---------- +**Query - Format** +**Link:** +https://www.firebase.com/docs/rest/api/#section-param-format + +**Description:** +If set to export, the server will encode priorities in the response. + +**Method:** + + set_format_response (option: detachable READABLE_STRING_8) + +**Example:** +To set the format query value to `export`: + + set_format_response("export") + +To unset the value: + + set_format_response(Void) + +---------- +**Query - Download** +**Link:** +https://www.firebase.com/docs/rest/api/#section-param-download + +**Description:** +Supported by GET only. If you would like to trigger a file download of your data from a web browser, add download=. This will cause our REST service to add the appropriate headers so that browsers know to save the data to a file. + +**Method: ** + + download (a_path: detachable READABLE_STRING_8; filename: detachable READABLE_STRING_8): detachable RESPONSE + +**Example: ** + + download ("/keyA", "file.txt") + +**Note:** +This command needs to be tested on another user's computer. The curl command listed in the documentation is: + + curl 'https://samplechat.firebaseio-demo.com/.json?download=myfilename.txt' + +When the command was executed on my terminal, no file was downloaded. + +---------- +**Query - Order by** +**Link:** +https://www.firebase.com/docs/rest/api/#section-param-orderby, +https://www.firebase.com/docs/rest/guide/retrieving-data.html#section-rest-ordered-data + +**Description:** +Data can be ordered using one of three filtering parameters. + + 1. Key - When using the `orderBy="$key"` parameter to sort your data, data will be returned in ascending order by key name. + 2. Value - When using the `orderBy="$value"` parameter to sort your data, children will be ordered by their value. + 3. Priority - When using the `orderBy="$priority"` parameter to sort your data, the ordering of children is determined by their priority and key as follows. Keep in mind that priority values can only be numbers or strings. + +**Method:** + + set_order_by_type (value: detachable READABLE_STRING_8) + +**Example:** +By default, the print format query value is not set. To set the order by query value to `key`: + + set_order_by_type ("key") + +To set the value to `value`: + + set_order_by_type ("value") + +To set the value to `priority`: + + set_order_by_type ("priority") + +To unset the value: + + set_order_by_type (Void) + +---------- +**Query - Start at, End at, Equal to, Limit to First, Limit to Last** +**Link:** +https://www.firebase.com/docs/rest/api/#section-query-parameters, https://www.firebase.com/docs/rest/guide/retrieving-data.html#section-rest-filtering + +**Description:** +We can construct queries to filter data based on various factors. To start, you specify how you want your data to be filtered using the orderBy parameter. Then, you combine orderBy with any of the other five parameters: limitToFirst, limitToLast, startAt, endAt, and equalTo. + +**Method:** +By default, none of the five parameters are set. To set the limitToFirst query value: + + set_limit_to_first_value (value: INTEGER) + +To set the limitToLast value: + + set_limit_to_last_value (value: INTEGER) + +To set the startAt value: + + set_start_at_value (value: detachable READABLE_STRING_8) + +To set the endAt value: + + set_end_at_value (value: detachable READABLE_STRING_8) + +To set the equalTo value: + + set_equal_to_value (value: detachable READABLE_STRING_8) + +**Note:** +If multiple values are set and need to be unset, a helper method is implemented to simultaneously clear all ranged, filtering values: + + clear_filtering_values + +---------- +**Security and Rules** +**Link:** +https://www.firebase.com/docs/rest/api/#section-security-rules, +https://www.firebase.com/docs/security/guide/securing-data.html + +**Description:** +The REST API can also be used to retrieve and update the Security and Firebase Rules for your Firebase app. You'll need your Firebase app's secret, which you can find under the Secrets panel of your Firebase app's dashboard. + +**Method:** + To retrieve the rules that are set for the Firebase app: + + + retrieve_rules: detachable RESPONSE + + To update the rules for the Firebase app: + + + update_rules (a_value: READABLE_STRING_8): detachable RESPONSE + +**Example:** +An example of updating rules is: + + update_rules("{%"rules%": {%".read%": true}}") + +which allows the database to be readable by the public. + +**Notes:** +If rules aren't set for `.read` or `.write` values, then they are automatically set to `false`. + +---------- +#### **Partially-Developed Features** + +---------- +**Priorities** +**Link:** +https://www.firebase.com/docs/rest/api/#section-priorities + +**Description:** +Priority information for a location can be referenced with a "virtual child" named .priority. Priorities can be read with GET requests and written with PUT requests. + +**Method:** + + get_priority (a_path: detachable READABLE_STRING_8): detachable RESPONSE + +**Example:** + + get_priority ("/keyA") + +**Notes:** +This feature is regarded as partially implemented, as a PUT-priority method was not implemented. The reason that this was the case was because the priority could be simply added to the JSON payload, such that: + + put (Void, "{%"name%": {%"first%": %"Tom%"}, %".priority%": 1.0}") + +---------- +#### **Incomplete Features** +The following features have not been implemented. + +---------- +**Query - Callback** +**Link:** +https://www.firebase.com/docs/rest/api/#section-param-callback + +**Description:** +Supported by GET only. To make REST calls from a web browser across domains, you can use JSONP to wrap the response in a JavaScript callback function. Add callback= to have the REST API wrap the the returned data in the callback function you specify. + +**Notes:** +This feature is a JavaScript only feature which is why it was not implemented. + +---------- +**Server Values** +**Link:** https://www.firebase.com/docs/rest/api/#section-server-values + +**Description:** +Server values can be written at a location using a placeholder value which is an object with a single .sv key. The value for that key is the type of server value you wish to set. + +**Notes:** +This feature is essentially values that can be used by the public. According to the documentation, there is currently one server value (timestamp). This value can be used, as shown in the following: + + put (Void, "{%".sv%": %"timestamp%"}") + +---------- +**Stream** +**Link:** https://www.firebase.com/docs/rest/api/#section-streaming + +**Description:** +Firebase REST endpoints support the EventSource / Server-Sent Events protocol. In return, the server will send named events as the state of the data at the requested URL changes. + +**Notes:** +In the original spec discussed with the project supervisor, stream was an optional feature to be developed if time permitted. + +---------- +#### **Future Development** +With my 120 hours of project time, I learnt about Eiffel and RESTful services, set up an appropriate development environment for Eiffel, implemented most of the Firebase API features as well as tested and wrote the required documentation. With more time, I would have considered implementing the following: + + +---------- + +**Stream (https://www.firebase.com/docs/rest/api/#section-streaming)** +This feature was briefly looked into and a basic implementation was attempted. According to the documentation, a few things need to be achieved. + + - Set the client's Accept header to "text/event-stream" + - Respect HTTP Redirects, in particular HTTP status code 307 + - If the location requires permission to read, you must include the auth parameter + +These steps were implemented however, after looking through Python and Ruby implementations of this feature, I realised that this was not enough. The implementation suggested that it would be necessary to incorporate threads into the feature. The first thread would be used to send items to the database while the second thread would notifications when the data was updated. + +Python Implementation - https://github.com/firebase/EventSource-Examples/tree/master/python +Ruby Implementation - https://github.com/firebase/EventSource-Examples/tree/master/ruby + + ---------- +**Minimise Query Errors** +Many of the query features cannot be used by all 5 REST methods. For example, the orderBy query can only be used with the GET method. If it is used with another REST method such as PUT, the following response will be returned: + +`{"error" : "Querying related parameters not supported on this request type"}` + +As sending a request to the server can take time, it would be optimal to drop requests that are going to fail before they are sent. This can be achieved through the utilisation of assertions and postconditions, resulting in a higher level of performance. + +---------- +**Testing** +The original plan was to test all 5 REST methods being used together. However, this has been a rather difficult task as I have been getting an unexpected result. + +I executed the following commands in application.e. + + response := api.delete(Void) + response := api.put (Void, "{%"keyA%": %"valueA%"}") + response := test.test_get ("/keyA", "valueA") + +This was the results from application.e. +(https://raw.githubusercontent.com/blank23/Redwood/b23efe72ced54ebdf2811447a8fa82b618621b46/other/Result%20of%20Executing%20Application.png) + +Afterwards, I executed the equivalent comments in a shell script test.sh. + + curl -X DELETE "https://samplechat.firebaseio-demo.com/.json" + curl -X PUT -d '{"keyA": "valueA"}' "https://samplechat.firebaseio-demo.com/.json" + curl "https://samplechat.firebaseio-demo.com/keyA.json" + +This was the result from the shell script. +(https://raw.githubusercontent.com/blank23/Redwood/b23efe72ced54ebdf2811447a8fa82b618621b46/other/Result%20of%20Executing%20Bash%20Script.png) + +The result from the shell script was the result that I had expected from executing application.e. From evaluation, I believe that the application class did not provide the expected results due to asynchronous issues. This issue has led to assertion failures and has hindered me from completely validating the tests in the test_rest_api.e file. I have created tests for GET, PUT and DELETE. Once this is resolved, tests should be also created for POST and PATCH. + +Tests can also be created to check if URIs with various query strings provide a correct result. However, many of the queries (print format, shallow, export, download) are easily examined by execution and visual inspection. + +I have a file called unofficial_application.e which is the file that I used to unofficially test my queries during the development phase. diff --git a/Readme.md b/Readme.md deleted file mode 100644 index 2f4896c..0000000 --- a/Readme.md +++ /dev/null @@ -1,4 +0,0 @@ -Firebase REST API. Eiffel Client - -Based on Firebase REST API: https://www.firebase.com/docs/rest/api/ - diff --git a/other/Result of Executing Application.png b/other/Result of Executing Application.png new file mode 100644 index 0000000..4af5036 Binary files /dev/null and b/other/Result of Executing Application.png differ diff --git a/other/Result of Executing Bash Script.png b/other/Result of Executing Bash Script.png new file mode 100644 index 0000000..460c60d Binary files /dev/null and b/other/Result of Executing Bash Script.png differ diff --git a/other/test.sh b/other/test.sh new file mode 100644 index 0000000..ee476d3 --- /dev/null +++ b/other/test.sh @@ -0,0 +1,3 @@ +curl -X DELETE "https://samplechat.firebaseio-demo.com/.json" +curl -X PUT -d '{"keyA": "valueA"}' "https://samplechat.firebaseio-demo.com/.json" +curl "https://samplechat.firebaseio-demo.com/keyA.json" diff --git a/src/firebase_api.e b/src/firebase_api.e index 1e67d3f..f1a418f 100644 --- a/src/firebase_api.e +++ b/src/firebase_api.e @@ -3,16 +3,16 @@ note date: "$Date$" revision: "$Revision$" EIS: "name=Firebase REST API", "src=https://www.firebase.com/docs/rest/api/", "protocol=uri" + class FIREBASE_API inherit - SHARED_EJSON + REFACTORING_HELPER create - make, - make_with_auth + make, make_with_auth feature {NONE} -- Initialization @@ -39,14 +39,54 @@ feature {NONE} -- Initialization feature -- Access base_uri: READABLE_STRING_8 - -- base uri. - - auth: READABLE_STRING_8 - -- firebase authentication token or app's secret. + -- Base uri. Firebase_api_json_extension: STRING_8 = ".json" -- URL firebase extension. + auth: READABLE_STRING_8 + -- Firebase authentication token or app's secret. + + query_count: INTEGER + -- Number of queries in uri. + + priority_uri_path: STRING_8 = "/.priority" + -- Uri path for viewing priority. + + security_uri_path: STRING_8 = ".settings/rules" + -- Uri path for viewing and updating security and rules. + + print_format: detachable READABLE_STRING_8 + -- Formats the data returned in the response from the server. + -- Value is either "pretty", "silent" or Void. + + is_shallow: BOOLEAN + -- Limits the depth of the response if set to true. + + format_response: detachable READABLE_STRING_8 + -- Server will encode priorities in response, if format is set to export. + + order_by: detachable READABLE_STRING_8 + -- Indicates how data is ordered and filtered. + + start_at: detachable READABLE_STRING_8 + -- Indicates start point for queries. + + end_at: detachable READABLE_STRING_8 + -- Indicates end point for queries. + + equal_to: detachable READABLE_STRING_8 + -- Indicates equality value for queries. + + limit_to_first: detachable READABLE_STRING_8 + -- Indicates first limit range for queries. + + limit_to_last: detachable READABLE_STRING_8 + -- Indicates last limit range for queries. + + download_filename: detachable READABLE_STRING_8 + -- Name of the download file. + feature -- REST API get (a_path: detachable READABLE_STRING_8): detachable RESPONSE @@ -58,12 +98,11 @@ feature -- REST API Result := l_request.execute end - - put (a_path: detachable READABLE_STRING_8; a_value: READABLE_STRING_8): detachable RESPONSE - -- Writing data. - require - is_json_value: is_valid_json (a_value) - local + put (a_path: detachable READABLE_STRING_8; a_value: READABLE_STRING_8): detachable RESPONSE + -- Writing data. + require + is_json_value: is_valid_json (a_value) + local l_request: REQUEST do create l_request.make ("PUT", new_uri (a_path)) @@ -71,11 +110,11 @@ feature -- REST API Result := l_request.execute end - post (a_path: detachable READABLE_STRING_8; a_value: READABLE_STRING_8): detachable RESPONSE - -- Pushing Data. - require - is_json_value: is_valid_json (a_value) - local + post (a_path: detachable READABLE_STRING_8; a_value: READABLE_STRING_8): detachable RESPONSE + -- Pushing Data. + require + is_json_value: is_valid_json (a_value) + local l_request: REQUEST do create l_request.make ("POST", new_uri (a_path)) @@ -83,11 +122,11 @@ feature -- REST API Result := l_request.execute end - patch (a_path: detachable READABLE_STRING_8; a_value: READABLE_STRING_8): detachable RESPONSE - -- Updating Data. - require - is_json_value: is_valid_json (a_value) - local + patch (a_path: detachable READABLE_STRING_8; a_value: READABLE_STRING_8): detachable RESPONSE + -- Updating Data. + require + is_json_value: is_valid_json (a_value) + local l_request: REQUEST do create l_request.make ("PATCH", new_uri (a_path)) @@ -95,7 +134,6 @@ feature -- REST API Result := l_request.execute end - delete (a_path: detachable READABLE_STRING_8): detachable RESPONSE -- Removing Data. local @@ -105,7 +143,6 @@ feature -- REST API Result := l_request.execute end - feature -- Query is_valid_json (a_value: READABLE_STRING_8): BOOLEAN @@ -117,27 +154,280 @@ feature -- Query feature {NONE} -- Implementation new_uri (a_path: detachable READABLE_STRING_8): STRING_32 - -- new uri (base_uri + a_path) + -- Builds uri. local - l_path : STRING_32 - l_query_string: STRING_32 + l_path: STRING_32 + l_query: STRING_32 + query_punctuation: STRING_8 do if attached a_path as ll_path then l_path := ll_path else l_path := "" end + if l_path.same_string("") then + l_path := "/" + end + if not l_path.is_empty and then not (l_path.starts_with ("/") or l_path.starts_with ("\")) then - l_path.prepend("/") + l_path.prepend ("/") end - Result := base_uri + l_path + Firebase_api_json_extension + l_query := "" + query_count := 0 + if attached print_format as ll_print then + query_punctuation := get_query_punctuation (query_count) + l_query.append (query_punctuation + "print=" + ll_print) + query_count := query_count + 1 + end + if attached format_response as ll_format then + query_punctuation := get_query_punctuation (query_count) + l_query.append (query_punctuation + "format=" + ll_format) + query_count := query_count + 1 + end + if attached order_by as ll_order_by then + query_punctuation := get_query_punctuation (query_count) + l_query.append (query_punctuation + "orderBy=" + ll_order_by) + query_count := query_count + 1 + end + if attached start_at as ll_start_at then + query_punctuation := get_query_punctuation (query_count) + l_query.append (query_punctuation + "startAt=" + ll_start_at) + query_count := query_count + 1 + end + if attached end_at as ll_end_at then + query_punctuation := get_query_punctuation (query_count) + l_query.append (query_punctuation + "endAt=" + ll_end_at) + query_count := query_count + 1 + end + if attached equal_to as ll_equal_to then + query_punctuation := get_query_punctuation (query_count) + l_query.append (query_punctuation + "equalTo=" + ll_equal_to) + query_count := query_count + 1 + end + if attached limit_to_first as ll_limit_to_first then + query_punctuation := get_query_punctuation (query_count) + l_query.append (query_punctuation + "limitToFirst=" + ll_limit_to_first) + query_count := query_count + 1 + end + if attached limit_to_last as ll_limit_to_last then + query_punctuation := get_query_punctuation (query_count) + l_query.append (query_punctuation + "limitToLast=" + ll_limit_to_last) + query_count := query_count + 1 + end + if is_shallow = True then + query_punctuation := get_query_punctuation (query_count) + l_query.append (query_punctuation + "shallow=true") + query_count := query_count + 1 + end + if attached download_filename as ll_download_filename then + query_punctuation := get_query_punctuation (query_count) + l_query.append (query_punctuation + "download=" + ll_download_filename) + query_count := query_count + 1 + end if not auth.is_empty then - Result.append ("?auth="+ auth ) + query_punctuation := get_query_punctuation (query_count) + l_query.append (query_punctuation + "auth=" + auth) + query_count := query_count + 1 + end + Result := base_uri + l_path + Firebase_api_json_extension + l_query + -- print (Result) + ensure + valid_query_count: query_count >= 0 + end + + get_query_punctuation (number_of_queries: INTEGER): STRING_8 + -- Returns the appropriate punctuation to the URI builder, based on the number of queries that have been already added to the URI string. + do + if number_of_queries = 0 then + Result := "?" + else + Result := "&" end end + +feature -- Print Format + + set_print_format (option: detachable READABLE_STRING_8) + -- Set data format that is returned from the server. + do + if option /= Void then + print_format := option + else + print_format := Void + end + ensure + valid_option: attached option as l_format and then (l_format.same_string ("pretty") or l_format.same_string ("silent") or option = Void) + end + +feature -- Shallow + + set_shallow (option: BOOLEAN) + -- Returns response values as true. Used to work with large datasets. + do + is_shallow := option + end + +feature -- Format Response + + set_format_response (option: detachable READABLE_STRING_8) + -- Encodes priorities in response. + do + if option /= Void then + format_response := option + end + ensure + valid_option: attached option as l_option and then l_option.same_string ("export") + end + +feature -- Filtering + + set_order_by_type (value: detachable READABLE_STRING_8) + -- The order_by value sets how data can be filtered and ordered. + do + if value /= Void then + order_by := "%"$" + value + "%"" + else + order_by := Void + end + ensure + valid_value: attached value as l_value and then (l_value.same_string ("key") or l_value.same_string ("value") or l_value.same_string ("priority")) + end + + set_start_at_value (value: detachable READABLE_STRING_8) + -- Sets start point for queries. + do + if value /= Void then + if value.is_integer = TRUE then + start_at := value + else + start_at := "%"" + value + "%"" + end + else + start_at := Void + end + end + + set_end_at_value (value: detachable READABLE_STRING_8) + -- Sets end point for queries. + do + if value /= Void then + if value.is_integer = TRUE then + end_at := value + else + end_at := "%"" + value + "%"" + end + else + end_at := Void + end + end + + set_equal_to_value (value: detachable READABLE_STRING_8) + -- Sets equality value for queries. + do + if value /= Void then + if value.is_integer = TRUE then + equal_to := value + else + equal_to := "%"" + value + "%"" + end + else + equal_to := Void + end + end + + set_limit_to_first_value (value: INTEGER) + -- Sets first limit range for queries. + do + limit_to_first := value.out + end + + set_limit_to_last_value (value: INTEGER) + -- Sets last limit range for queries. + do + limit_to_last := value.out + end + + clear_filtering_values + do + order_by := Void + start_at := Void + end_at := Void + equal_to := Void + limit_to_first := Void + limit_to_last := Void + end + +feature -- Download + + download (a_path: detachable READABLE_STRING_8; filename: detachable READABLE_STRING_8): detachable RESPONSE + -- Triggers a file download of the data. + do + if attached filename as ll_filename then + download_filename := ll_filename + ".txt" + else + download_filename := "file.txt" + end + Result := get (a_path) + download_filename := Void + end + +feature -- Priority + + get_priority (a_path: detachable READABLE_STRING_8): detachable RESPONSE + -- Reads data priority. + do + if attached a_path as ll_path then + Result := get (ll_path + priority_uri_path) + else + Result := get (priority_uri_path) + end + end + +feature -- Rules and Security + + retrieve_rules: detachable RESPONSE + -- Reads rules. + do + Result := get (security_uri_path) + end + + update_rules (a_value: READABLE_STRING_8): detachable RESPONSE + -- Updates rules. + do + Result := put (security_uri_path, a_value) + end + +feature -- Stream + + stream (a_path: detachable READABLE_STRING_8): detachable RESPONSE + local + l_request: REQUEST + do + fixme ("TODO: Need to implement two threads for receiving notifications and sending data.") + create l_request.make ("GET", new_uri (a_path)) + l_request.add_header (l_request.accept_type_header_name, l_request.default_accept_type) + Result := l_request.execute + end + +feature -- Clear + + clear_all_query_values + -- Clears all query values. + do + print_format := Void + is_shallow := False + format_response := Void + order_by := Void + start_at := Void + end_at := Void + equal_to := Void + limit_to_first := Void + limit_to_last := Void + end + note - copyright: "2011-2015 Javier Velilla, Jocelyn Fiat, Eiffel Software and others" + copyright: "2011-2016 Javier Velilla, Jocelyn Fiat, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" source: "[ Eiffel Software @@ -146,4 +436,5 @@ note Website http://www.eiffel.com Customer support http://support.eiffel.com ]" + end diff --git a/src/request/request.e b/src/request/request.e index 0b65e91..de155f7 100644 --- a/src/request/request.e +++ b/src/request/request.e @@ -49,7 +49,7 @@ feature -- Status Report end sanitized_url: STRING_8 - -- Returns the URL without the query string part. + -- Returns the URL without the query string part. local l_uri: URI do @@ -95,6 +95,13 @@ feature -- Constants Result := application_json end + accept_type_header_name: STRING_8 = "Accept"; + + default_accept_type: STRING_8 + once + Result := "text/event-stream" + end + feature -- Access uri: STRING_8 diff --git a/test/application.e b/test/application.e index a3465a4..554f4e8 100644 --- a/test/application.e +++ b/test/application.e @@ -1,7 +1,7 @@ note - description : "test_redwood application root class" - date : "$Date$" - revision : "$Revision$" + description: "Application" + date: "$Date$" + revision: "$Revision$" class APPLICATION @@ -14,59 +14,31 @@ create feature {NONE} -- Initialization - make - -- Run application. + make local - l_firebase: FIREBASE_API - do - create l_firebase.make ("https://SampleChat.firebaseIO-demo.com") - -- Get - if attached {RESPONSE} l_firebase.get ("/users/jack/name") as l_response then - print ("%NGET") - print ("%NStatus:"+l_response.status.out +"%N") - if attached l_response.body as l_body then - print ("%NBody:"+l_body+"%N") - end - end - - -- Put - if attached {RESPONSE} l_firebase.put ("/users/jack/name","{ %"first%": %"Jack%", %"last%": %"Sparrow%" }") as l_response then - print ("%NPUT") - print ("%NStatus:"+l_response.status.out +"%N") - if attached l_response.body as l_body then - print ("%NBody:"+l_body+"%N") - end - end - - -- Post - if attached {RESPONSE} l_firebase.post ("/users/jack/name","{%"user_id%" : %"jack%", %"text%" : %"Ahoy!%"}") as l_response then - print ("%NPOST") - print ("%NStatus:"+l_response.status.out +"%N") - if attached l_response.body as l_body then - print ("%NBody:"+l_body+"%N") - end - end - - - -- Patch - if attached {RESPONSE} l_firebase.patch ("/users/jack/name/","{%"last%":%"Jones%"}") as l_response then - print ("%NPATCH") - print ("%NStatus:"+l_response.status.out +"%N") - if attached l_response.body as l_body then - print ("%NBody:"+l_body+"%N") - end - end - - -- Delete - if attached {RESPONSE} l_firebase.delete ("/users/jack/name/last") as l_response then - print ("%NDELETE") - print ("%NStatus:"+l_response.status.out +"%N") - if attached l_response.body as l_body then - print ("%NBody:"+l_body+"%N") - end - end - - - end + api: FIREBASE_API + response: detachable RESPONSE + test: TEST_REST_API + do + create test + create api.make ("https://samplechat.firebaseio-demo.com") + + -- Testing GET + -- Clearing database, putting objects into the database and then testing. + response := api.delete(Void) + response := api.put (Void, "{%"keyA%": %"valueA%"}") + response := test.test_get ("/keyA", "valueA") + + -- Testing PUT + -- Clearing database and then testing. + -- response := api.delete(Void) + -- response := test.test_put (Void, "{%"keyA%": %"valueA%"}", "valueA", "/keyA") + + -- Testing DELETE + -- Clearing database, putting objects into the database and then deleting. + -- response := api.delete(Void) + -- response := api.put (Void, "{%"keyA%": %"valueA%"}") + -- response := test.test_delete ("/keyA") + end end diff --git a/test/test_redwood.ecf b/test/test_redwood.ecf index 6f1d849..7e6fa04 100644 --- a/test/test_redwood.ecf +++ b/test/test_redwood.ecf @@ -1,19 +1,20 @@ - + - - diff --git a/test/test_rest_api.e b/test/test_rest_api.e new file mode 100644 index 0000000..d9a188e --- /dev/null +++ b/test/test_rest_api.e @@ -0,0 +1,127 @@ +note + description: "[ + Eiffel tests that can be executed by testing tool. + ]" + author: "EiffelStudio test wizard" + date: "$Date$" + revision: "$Revision$" + testing: "type/manual" + +class + TEST_REST_API + +inherit + EQA_TEST_SET + +redefine + on_prepare + +end + +feature {NONE} -- Initialization + on_prepare + do + make ("https://samplechat.firebaseio-demo.com", Void) + end + + make (a_base_uri: READABLE_STRING_8; a_auth: detachable READABLE_STRING_8) + do + base_uri := a_base_uri + auth := a_auth + if attached auth as ll_auth then + create api.make_with_auth(base_uri, ll_auth) + else + create api.make(base_uri) + end + end + +feature -- Access + + base_uri: READABLE_STRING_8 + -- Base uri. + + auth: detachable READABLE_STRING_8 + -- Firebase authentication token or app's secret. + + api: FIREBASE_API + -- Firebase API + +feature -- Test routines + + test_get (a_path: detachable READABLE_STRING_8; expected_value: READABLE_STRING_8): detachable RESPONSE + -- Test GET + local + value: READABLE_STRING_8 + body: READABLE_STRING_8 + do + -- GET item from the database. + Result := api.get (a_path) + + -- Ensures the response value is the same as the expected value. + value := "%"" + expected_value + "%"" + if Result /= Void then + if attached Result.body as ll_body then + body := ll_body + else + body := "" + end + assert ("Checking GET response.", body.same_string (value)) + end + end + + test_put (a_path: detachable READABLE_STRING_8; a_value: READABLE_STRING_8; expected_value: READABLE_STRING_8; path_to_check: detachable READABLE_STRING_8): detachable RESPONSE + -- Test PUT + local + value: READABLE_STRING_8 + body: READABLE_STRING_8 + do + -- PUT item into the database. + Result := api.put (a_path, a_value) + + -- Checks that the correct item is in the database. + value := "%"" + expected_value + "%"" + Result := api.get (path_to_check) + if Result /= Void then + if attached Result.body as ll_body then + body := ll_body + else + body := "" + end + assert ("Checking PUT response.", body.same_string (value)) + end + end + + test_delete (a_path: detachable READABLE_STRING_8): detachable RESPONSE + -- TEST DELETE + local + body: READABLE_STRING_8 + do + -- Ensures that the path originally exists. + Result := api.get (a_path) + if Result /= Void then + if attached Result.body as ll_body then + body := ll_body + else + body := "" + end + assert ("Checking path exists.", not body.same_string ("null")) + end + + -- Ensures that the delete is successful, through status response. + Result := api.delete (a_path) + if Result /= Void then + assert ("Checking delete is successful.", Result.status = 200) + end + + -- Ensures that the delete is successful, through a GET that returns null. + Result := api.get (a_path) + if Result /= Void then + if attached Result.body as ll_body then + body := ll_body + else + body := "" + end + assert ("Checking GET response.", body.same_string ("null")) + end + end +end diff --git a/test/unofficial_application.e b/test/unofficial_application.e new file mode 100644 index 0000000..f8bf921 --- /dev/null +++ b/test/unofficial_application.e @@ -0,0 +1,90 @@ +note + description: "Application" + date: "$Date$" + revision: "$Revision$" + +class + UNOFFICIAL_APPLICATION + +inherit + ARGUMENTS + +create + make + +feature {NONE} -- Initialization + + make + local + api: FIREBASE_API + value: READABLE_STRING_8 + response: detachable RESPONSE + test: TEST_REST_API + do + -- Create API + create api.make("https://samplechat.firebaseio-demo.com") + + -- Test GET + -- api.set_print_format("pretty") + -- api.set_print_format(Void) + + -- api.set_shallow(True) + + -- api.set_format_response("export") + + -- api.set_order_by_type("key") + -- api.set_start_at_value("d") + -- api.set_end_at_value("i") + -- api.set_end_at_value(Void) + -- api.set_equal_to_value("jim") + + -- api.set_order_by_type("value") + -- api.set_start_at_value("2") + + -- api.set_limit_to_first_value(3) + -- api.clear_filtering_values() + -- api.clear_all_query_settings() + + -- response := api.get("/scores") + + -- TEST PUT + -- value := "{%".sv%": %"timestamp%"}" + -- value := "{%".value%": 2, %".priority%": 6.0}" + -- response := api.put("/scores/Duo", value) + + -- TEST POST + -- value := "{%"Zoe%": 2}" + -- response := api.post("/scores", value) + + -- TEST PATCH + -- value := "{%"Zoe%": 2}" + -- response := api.patch("/scores", value) + + -- TEST DELETE + -- response := api.delete("keyB") + + -- TEST METHOD OVERRIDE + -- response := api.set_method_override("scores", Void, "GET") + + -- TEST DOWNLOAD + -- response := api.download(Void, "file") + + -- TEST GET_PRIORITIES + -- response := api.get_priority("/scores/Haribo") + + -- TEST RETRIEVE_RULES + -- response := api.retrieve_rules() + + -- TEST UPDATE_RULES + -- value := "{%"rules%": {%".read%": true}}" + -- response := api.update_rules("{%"rules%": {%".read%": true}}") + + -- TEST STREAM + -- response := api.stream("/cool") + + if response /= Void then + print (response.body) + end + end + +end