*: remove newly superfluous static types on attributes
[nit.git] / lib / curl / curl.nit
index b77b0f1..57a2423 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Curl services: `CurlHTTPRequest` and `CurlMail`
+# Data transfer with URL syntax
+#
+# Download or upload over HTTP with `CurlHTTPRequest` and send emails with `CurlMail`.
 module curl
 
 import native_curl
 
+redef class Sys
+       # Shared Curl library handle
+       #
+       # Usually, you do not have to use this attribute, it instancied by `CurlHTTPRequest` and `CurlMail`.
+       # But in some cases you may want to finalize it to free some small resources.
+       # However, if Curl services are needed once again, this attribute must be manually set.
+       var curl: Curl = new Curl is lazy, writable
+end
+
 # Curl library handle, it is initialized and released with this class
 class Curl
        super FinalizableOnce
 
-       protected var native = new NativeCurl.easy_init
+       private var native = new NativeCurl.easy_init
 
        # Check for correct initialization
        fun is_ok: Bool do return self.native.is_init
@@ -34,14 +45,11 @@ end
 # CURL Request
 class CurlRequest
 
-       private var curl: Curl
+       private var curl: Curl = sys.curl
 
        # Shall this request be verbose?
        var verbose: Bool = false is writable
 
-       # Launch request method
-       fun execute: CurlResponse is abstract
-
        # Intern perform method, lowest level of request launching
        private fun perform: nullable CurlResponseFailed
        do
@@ -65,24 +73,54 @@ class CurlRequest
        end
 end
 
-# CURL HTTP Request
+# HTTP request builder
+#
+# The request itself is sent by either `execute` or `download_to_file`.
+# The attributes of this class must be set before calling either of these two methods.
+#
+# ## Minimal usage example
+#
+# ~~~
+# var request = new CurlHTTPRequest("http://example.org/")
+# var response = request.execute
+# if response isa CurlResponseSuccess then
+#     print "Response status code: {response.status_code}"
+#     print response.body_str
+# else if response isa CurlResponseFailed then
+#     print_error response.error_msg
+# end
+# ~~~
 class CurlHTTPRequest
        super CurlRequest
        super NativeCurlCallbacks
 
+       # Address of the remote resource to request
        var url: String
-       var datas: nullable HeaderMap = null is writable
-       var headers: nullable HeaderMap = null is writable
-       var delegate: nullable CurlCallbacks = null is writable
+
+       # Data for the body of a POST request
+       var data: nullable HeaderMap is writable
+
+       # Raw body string
+       #
+       # Set this value to send raw data instead of the POST formatted `data`.
+       #
+       # If `data` is set, the body will not be sent.
+       var body: nullable String is writable
+
+       # Header content of the request
+       var headers: nullable HeaderMap is writable
+
+       # Delegates to customize the behavior when running `execute`
+       var delegate: nullable CurlCallbacks is writable
 
        # Set the user agent for all following HTTP requests
-       fun user_agent=(name: String)
-       do
-               curl.native.easy_setopt(new CURLOption.user_agent, name)
-       end
+       var user_agent: nullable String is writable
 
-       # Execute HTTP request with settings configured through attribute
-       redef fun execute
+       # Execute HTTP request
+       #
+       # By default, the response body is returned in an instance of `CurlResponse`.
+       # This behavior can be customized by setting a custom `delegate`.
+       fun execute: CurlResponse
        do
                if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")
 
@@ -98,6 +136,12 @@ class CurlHTTPRequest
                err = self.curl.native.easy_setopt(new CURLOption.url, url)
                if not err.is_ok then return answer_failure(err.to_i, err.to_s)
 
+               var user_agent = user_agent
+               if user_agent != null then
+                       err = curl.native.easy_setopt(new CURLOption.user_agent, user_agent)
+                       if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+               end
+
                # Callbacks
                err = self.curl.native.register_callback_header(callback_receiver)
                if not err.is_ok then return answer_failure(err.to_i, err.to_s)
@@ -114,18 +158,21 @@ class CurlHTTPRequest
                end
 
                # Datas
-               var datas = self.datas
-               if datas != null then
-                       var postdatas = datas.to_url_encoded(self.curl)
+               var data = self.data
+               if data != null then
+                       var postdatas = data.to_url_encoded(self.curl)
                        err = self.curl.native.easy_setopt(new CURLOption.postfields, postdatas)
                        if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+               else if body != null then
+                       err = self.curl.native.easy_setopt(new CURLOption.postfields, body.as(not null))
+                       if not err.is_ok then return answer_failure(err.to_i, err.to_s)
                end
 
                var err_resp = perform
                if err_resp != null then return err_resp
 
                var st_code = self.curl.native.easy_getinfo_long(new CURLInfoLong.response_code)
-               if not st_code == null then success_response.status_code = st_code.response
+               if not st_code == null then success_response.status_code = st_code
 
                return success_response
        end
@@ -156,7 +203,7 @@ class CurlHTTPRequest
                if not output_file_name == null then
                        opt_name = output_file_name
                else if not self.url.substring(self.url.length-1, self.url.length) == "/" then
-                       opt_name = self.url.basename("")
+                       opt_name = self.url.basename
                else
                        return answer_failure(0, "Unable to extract file name, please specify one")
                end
@@ -170,16 +217,16 @@ class CurlHTTPRequest
                if err_resp != null then return err_resp
 
                var st_code = self.curl.native.easy_getinfo_long(new CURLInfoLong.response_code)
-               if not st_code == null then success_response.status_code = st_code.response
+               if not st_code == null then success_response.status_code = st_code
 
                var speed = self.curl.native.easy_getinfo_double(new CURLInfoDouble.speed_download)
-               if not speed == null then success_response.speed_download = speed.response
+               if not speed == null then success_response.speed_download = speed
 
                var size = self.curl.native.easy_getinfo_double(new CURLInfoDouble.size_download)
-               if not size == null then success_response.size_download = size.response
+               if not size == null then success_response.size_download = size
 
                var time = self.curl.native.easy_getinfo_double(new CURLInfoDouble.total_time)
-               if not time == null then success_response.total_time = time.response
+               if not time == null then success_response.total_time = time
 
                success_response.file.close
 
@@ -188,6 +235,33 @@ class CurlHTTPRequest
 end
 
 # CURL Mail Request
+#
+# ~~~
+# # Craft mail
+# var mail = new CurlMail("sender@example.org",
+#                            to=["to@example.org"], cc=["bob@example.org"])
+#
+# mail.headers_body["Content-Type:"] = """text/html; charset="UTF-8""""
+# mail.headers_body["Content-Transfer-Encoding:"] = "quoted-printable"
+#
+# mail.body = "<h1>Here you can write HTML stuff.</h1>"
+# mail.subject = "Hello From My Nit Program"
+#
+# # Set mail server
+# var error = mail.set_outgoing_server("smtps://smtp.example.org:465",
+#                                      "user@example.org", "mypassword")
+# if error != null then
+#     print "Mail Server Error: {error}"
+#     exit 0
+# end
+#
+# # Send
+# error = mail.execute
+# if error != null then
+#     print "Transfer Error: {error}"
+#     exit 0
+# end
+# ~~~
 class CurlMail
        super CurlRequest
        super NativeCurlCallbacks
@@ -216,7 +290,10 @@ class CurlMail
        # Content header
        var headers_body = new HeaderMap is lazy, writable
 
-       private var supported_outgoing_protocol: Array[String] = ["smtp", "smtps"]
+       # Protocols supported to send mail to a server
+       #
+       # Default value at `["smtp", "smtps"]`
+       var supported_outgoing_protocol = ["smtp", "smtps"]
 
        # Helper method to add pair values to mail content while building it (ex: "To:", "address@mail.com")
        private fun add_pair_to_content(str: String, att: String, val: nullable String): String
@@ -268,7 +345,7 @@ class CurlMail
        end
 
        # Execute Mail request with settings configured through attribute
-       redef fun execute
+       fun execute: nullable CurlResponseFailed
        do
                if not self.curl.is_ok then return answer_failure(0, "Curl instance is not correctly initialized")
 
@@ -343,7 +420,7 @@ class CurlMail
                var err_resp = perform
                if err_resp != null then return err_resp
 
-               return new CurlMailResponseSuccess
+               return null
        end
 end
 
@@ -362,6 +439,8 @@ class CurlResponseFailed
 
        var error_code: Int
        var error_msg: String
+
+       redef fun to_s do return "{error_msg} ({error_code})"
 end
 
 # Success Abstract Response Success Class
@@ -395,19 +474,14 @@ class CurlResponseSuccess
        end
 end
 
-# Success Response Class of mail request
-class CurlMailResponseSuccess
-       super CurlResponseSuccessIntern
-end
-
 # Success Response Class of a downloaded File
 class CurlFileResponseSuccess
        super CurlResponseSuccessIntern
 
        var status_code = 0
-       var speed_download = 0
-       var size_download = 0
-       var total_time = 0
+       var speed_download = 0.0
+       var size_download = 0.0
+       var total_time = 0.0
        private var file: nullable FileWriter = null
 
        # Receive bytes stream from request due to stream callback registering