Using Avro throws a AvroRuntimeException Malformed data - clojure

I have the following code:
(defn parse-schema
"Returns an Avro schema"
^Schema$RecordSchema [^String schema-file]
(let [schema (File. schema-file)]
(.parse (Schema$Parser.) schema-file)))
(defn get-reader
"Returns a DatumReader"
^SpecificDatumReader [^Schema$RecordSchema schema]
(SpecificDatumReader. schema))
(defn byte-to-object
"Returns an object from a byte[]"
[reader message]
(let [ decoder (.binaryDecoder (DecoderFactory/get) message nil) ]
(.read reader nil decoder)))
Using the code in repl:
plugflow.main=> (avro/parse-schema "schema/test.avsc")
#object[org.apache.avro.Schema$RecordSchema 0x6e896dd7 "{\"type\":\"record\",\"name\":\"test\",\"namespace\":\"com.streambright.avro\",\"fields\":[{\"name\":\"user_name\",\"type\":\"string\",\"doc\":\"User name of any user\"}],\"doc:\":\"Nothing to see here...\"}"]
plugflow.main=> (def record-schema (avro/parse-schema "schema/test.avsc"))
#'plugflow.main/record-schema
plugflow.main=> (avro/get-reader record-schema)
#object[org.apache.avro.specific.SpecificDatumReader 0x56b1cac6 "org.apache.avro.specific.SpecificDatumReader#56b1cac6"]
plugflow.main=> (def avro-reader (avro/get-reader record-schema))
#'plugflow.main/avro-reader
plugflow.main=> (import '[java.nio.file Files Paths Path])
java.nio.file.Path
plugflow.main=> (import '[java.net URI])
java.net.URI
plugflow.main=> (def byte-arr (Files/readAllBytes (Paths/get (URI. "file:///data/test.avro"))))
#'plugflow.main/byte-arr
plugflow.main=> (avro/byte-to-object avro-reader byte-arr))
AvroRuntimeException Malformed data. Length is negative: -40 org.apache.avro.io.BinaryDecoder.doReadBytes (BinaryDecoder.java:336)
Using Avro CLI:
java -jar avro-tools-1.8.1.jar tojson data/test.avro
log4j:WARN No appenders could be found for logger (org.apache.hadoop.metrics2.lib.MutableMetricsFactory).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
{"user_name":"tibi"}
What am I missing?

It turns out there are two set of Avro classes one for reading and writing Avro files and one for reading and writing Avro encoded messages. In case of using the avro-cli it writes a proper Avro file that has the schema included in it. When I was trying to read the file with the functions designed for dealing with the Avro encoded messages it obviously failed.
The right way of writing a single Avro message without the schema (in case you would like to use it in unit tests or integration test)
Schema schema = new Schema.Parser().parse("{\n \"type\": \"record\",\n \"name\": \"User\",\n \"namespace\": \"com.streambright\",\n \"fields\": [{\n \"name\": \"user_name\",\n \"type\": \"string\",\n \"doc\": \"User name of the user\"\n }, {\n \"name\": \"age\",\n \"type\": \"int\",\n \"doc\": \"Age of the user\"\n }, {\n \"name\": \"weight\",\n \"type\": \"float\",\n \"doc\": \"Weight of the user\"\n }, {\n \"name\": \"address\",\n \"type\": {\n \"type\": \"record\",\n \"name\": \"Address\",\n \"fields\": [{\n \"name\": \"street_address\",\n \"type\": \"string\"\n }, {\n \"name\": \"city\",\n \"type\": \"string\"\n }]\n }\n }],\n \"doc:\": \"Nothing to see here...\"\n}");
User user = new User();
user.setUserName("Tibi Kovacs");
user.setAge(25);
user.setWeight(((float) 32.12));
user.setAddress(new Address("FoxiMaxi St","Budapest"));
SpecificDatumWriter<User> avroEventWriter = new SpecificDatumWriter<User>(schema);
EncoderFactory avroEncoderFactory = EncoderFactory.get();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
BinaryEncoder binaryEncoder = avroEncoderFactory.binaryEncoder(stream, null);
avroEventWriter.write(user, binaryEncoder);
binaryEncoder.flush();
IOUtils.closeQuietly(stream);
byte[] m = stream.toByteArray();
FileOutputStream fos = new FileOutputStream("/full/path/data/test3.java.avro");
fos.write(m);
fos.close();

Related

Creating a new text file in a diretory using org-capture

Following this response: Org-Mode - How do I create a new file with org-capture?
I am trying to make this piece of code work, but I get the error: invalid file location: nil.
(defun capture-report-data-file (path)
(let ((name (read-string "Name: ")))
(expand-file-name (format "%s-%s.txt"
(format-time-string "%Y-%m-%d")
name) path)))
'(("t"
"todo"
entry
(file (capture-report-date-file "~/path/path/name"))
"* TODO")))
Actually, it works like this:
Replace
(file (capture-report-date-file "~/path/path/name"))
with
(file (lambda () (capture-report-date-file "~/path/path/name")))

Invalid Info Type

Getting the following error for the info type: ENCRYPTION_KEY.
{\n \"code\": 400,\n \"message\": \"Invalid built-in info type name \\"ENCRYPTION_KEY\\".\",\n \"status\": \"INVALID_ARGUMENT\"\n }\n}\n","errorVerbose":"DLP non 200. Body: {\n \"error\": {\n \"code\": 400,\n \"message\": \"Invalid built-in info type name \\"ENCRYPTION_KEY\\".\",\n \"status\": \"INVALID_ARGUMENT\"\n }
Is this a supported detector as mentioned on the dlp info type page? https://cloud.google.com/dlp/docs/infotypes-reference#united_states
That is a bug ... our documentation got updated before we were ready to launch that. Oops! For a source of truth, you can check https://developers.google.com/apis-explorer/#p/dlp/v2/dlp.infoTypes.list?_h=1& as to what's available.

How to show error on missing parameters for options?

I'm an absolute Clojure beginner and I'm trying to build a CLI app using the clojure.tools.cli library.
My problem is that I can't show any error when an option is not provided with required parameter.
What I want:
$ java -jar test.jar -m SAMPLE
Given file: SAMPLE
$ java -jar test.jar -m
ERROR: Please provide a file
What happens:
$ java -jar test.jar -m SAMPLE
Given file: SAMPLE
$ java -jar test.jar -m
$
It doesn't show anything.
Here is my code:
(ns obmed-clj.core
(:require [clojure.tools.cli :refer [parse-opts]])
(:gen-class))
(def cli-options
[["-m" "--menu FILE" "Provide menu file path"
:parse-fn #(if (nil? %)
(println "ERROR: Please provide a file")
%)
:validate-fn #(println "Given file:" %)]])
(defn -main [& args]
(parse-opts args cli-options))
You are abusing the -fn arguments here a little. Their use is to convert the "string" (in your case, since you have "--menu FILE") and then do additional validation on that (but rather use :validate [fn msg] instead). So e.g.:
user=> (def cli-opts [["-m" "--menu FILE" "menu file"
:parse-fn #(java.io.File. %)
:validate [#(.exists %) "file must exist"]]])
#'user/cli-opts
Missing argument:
user=> (parse-opts ["-m"] cli-opts)
{:arguments [],
:errors ["Missing required argument for \"-m FILE\""],
:options {},
:summary " -m, --menu FILE menu file"}
File not existing:
user=> (parse-opts ["-m" "XXX"] cli-opts)
{:arguments [],
:errors ["Failed to validate \"-m XXX\": file must exist"],
:options {},
:summary " -m, --menu FILE menu file"}
All is well:
user=> (parse-opts ["-m" "/etc/hosts"] cli-opts)
{:arguments [],
:errors nil,
:options {:menu #<java.io.File#34d63c80 /etc/hosts>},
:summary " -m, --menu FILE menu file"}

422 Unprocessable Entity response when POSTing file upload in Clojure

I am trying to emulate this curl request
curl "https://{subdomain}.zendesk.com/api/v2/uploads.json?filename=myfile.dat&token={optional_token}" \
-v -u {email_address}:{password} \
-H "Content-Type: application/binary" \
--data-binary #file.dat -X POST
with the following code
(POST "/uploads" request
(let [filename (get-in request [:params "file" :filename])
file (get-in request [:params "file" :tempfile])
url (str "https://REDACTED.zendesk.com/api/v2/uploads.json?filename=" filename)]
(clj-http.client/post url {:headers {"Content-Type" “application/binary”}
:multipart-params [{:name "file"
:content file
:mime-type "application/binary”}]})
but I am getting a ‘422 Unprocessable Entity’ response from Zendesk. The file/tempfile is coming in as #object[java.io.File 0x3768306f "/var/folders/l3/7by17gp51sx2gb2ggykwl9zc0000gn/T/ring-multipart-6501654841068837352.tmp"] on the request.
I have played with clojure.java.io coercions (like clojure.java.io/output-stream) as mentioned at Saving an image form clj-http request to file, but that didn't help.
(PS. I’m fairly certain I don’t need to auth because I can get the direct upload to Zendesk to work through Postman.)
After revisiting this, the solution was simple. Zendesk expects the request body to be binary (as the curl request indicates). So, in this case, I passed the image to my server as base64 encoded data (just as JSON).
I then used this library to convert the base64 string to a byte array: https://github.com/xsc/base64-clj
(defn byte-array-from-base64
[base64-string]
(base64/decode-bytes (.getBytes base64-string)))
Finally, you can simple pass the byte array to Zendesk as the body of the clj-http library request.
(client/post
"https://REDACTED.zendesk.com/api/v2/uploads.jsonfilename=filename.jpg"
{:headers {"Authorization" "Basic AUTHORIZATION_TOKEN"
"Content-Type" "application/binary"}
:body (byte-array-from-base64 base64-string)})

writing a test to check the file returned by a ring response

I have a compojure route which returns a file. I want to test -
1) If a file is returned.
2) The specific file that was returned.
When I run (app (ring.mock/request :get "/myroute")) I get
{:body #<File resources/public/templates/index.html>, :headers {"Content-Length" "2349", "Last-Modified" "Sat, 16 Mar 2013 11:01:03 GMT"}, :status 200}
How do I check that the returned value in the body is of a type file ? And getting more ambitious can I check it is the file located at 'resources/public/templates/index.html' ?
Ring requests are just maps, so you can extract the body with the :body keyword and then check it's type with type
(type (:body (app (ring.mock/request :get "/myroute"))))
(perhaps I'm not understanding the question though?)