I'm having trouble getting the sender of a message in SmallTalk. What I want to accomplish is to modify the return value of a method (A) from another method (B) which is called by the first one (A). Again... A calls B, and I want B to return a value from A's context.
Example code:
This would be A:
A
| aResult aPartialResult |
aPartialResult := self B.
"do things with aPartialResult"
^aResult.
And this would be B:
B
| aResult |
[ aResult := "do something" ]
on: Exception
do: ["make A return something"].
^aResult.
The thing is that I want to the exceptions that could be raised in B to be handled in B too. That's why I don't just raise an exception in B to handle it in A and easily return from there.
I thought I would be able to do this using thisContext, but the sender is nil. It wouldn't hurt to also get an answer on why is that...
Thanks in advance!
Guillermo, Exception handling can seamlessly replace a few bad ideas here:
using thisContext (which is almost never necessary and usually
a bad idea)
passing strings around e.g. '1|', UserInterface invalidCartIdErrorMessage
Using return: with those strings to pass errors along
Also, retrieveCart:onErrorReturnFrom: is doing too much. With all the error handlers, the actual logic gets lost.
So, the first thing I would to is create Error subclasses to represent your domain concepts e.g. AddBookError, CartExpiredError, InvalidCartError
Then you just set the error message, maybe like:
CartExpiredError>>initialize
super initialize.
self messageText: '1|', UserInterface cartHasExpiredErrorMessage.
The next thing (really two steps) is to replace the raw dictionary methods with private accessors, which can use your new Error classes, like so:
timestampFor: aCartId
^ cartCreationDateAndTime at: aCartId ifAbsent: [ InvalidCartError signal ].
and
cartNumber: aCartId
^ carts at: aCartId ifAbsent: [ InvalidCartError signal ].
Cart>>add: aQuantity booksWithISBN: aBookISBN
fail ifTrue: [ AddBookError signal ].
Now, retrieveCart:onErrorReturnFrom: can become:
retrieveCart: aCartId
| aCartCreationDateAndTime |
aCartCreationDateAndTime := self timestampFor: aCartId.
Time now > (aCartCreationDateAndTime + 30 minutes) ifTrue: [ CartExpiredError signal ].
^ self cartNumber: aCartId.
And finally, the greatly-simplified A becomes:
add: aQuantity booksWithISBN: aBookISBN toCart: aCartId
| aCart |
[aCart := self retrieveCart: aCartId.
aCart add: aQuantity booksWithISBN: aBookISBN]
on: Error
do: [ :e | ^ e messageText ].
^ '0|OK'.
This can still be cleaned (e.g. Make a superclass for all the Error classes that prepends '1|' to the messageText), and obviously you will have to work this simplified version into your actual project, but can you start to see how exceptions can make your life easier?
Here is a working mockup of the code, with passing tests on github
n.b. The one other thing I noticed was aCartCreationDateAndTime. It would seem more natural to have this be a property of the cart, but maybe that doesn't make sense in the actual application...
A simple way is that A pass a Block with a return inside to B, like :
A
| aResult aPartialResult |
aPartialResult := self BonSpecialConditionDo: [:partial | ^partial].
...snip...
Then
BonSpecialConditionDo: aBlock
| partialResult |
partialResult := self doSomethingPartial.
^[self doSomething]
on: SomeException
do: [:exc | aBlock value: partialResult]
Beware, catching Exception is considered dangerous (you catch too many things).
EDIT: I just removed an un-necessary return ^ inside the handler
EDIT: doing it with superpowers (but don't kill a fly with a hammer)
B
| partialResult |
partialResult := self doSomethingPartial.
^[self doSomething]
on: SomeException
do: [:exc | thisContext home sender home return: partialResult ]
I thought you could access thisContext through the Exception exc (this is the instance variable handlerContext), but there does not seem to be any convenient message for accessing this internal state...
Got it!
Here's A:
A
| aResult aPartialResult |
aPartialResult := self BOnErrorReturnFrom: thisContext.
"do things with aPartialResult"
^aResult.
And this would be B:
BOnErrorReturnFrom: aContext
| aResult |
[ aResult := "do something" ]
on: Exception
do: [aContext return: "whatever you want :)"].
^aResult.
It wasn't so hard after I realized that the keyword "thisContext", when used in a BlockClosure, doesn't return the ContextPart in which the block was declared but something else (still not sure what it is, though).
In response to Sean:
What I'm attempting to do with this is to avoid repetition of code. The object which implements A and B is the inner (model side) part of a REST interface, so I want it to return a string and only a string (I don't want exceptions nor anything different than a string object to get through). In my specific problem, A (sorry for breaking the Smalltalk message naming convention, but I think that changing that now will lead to further confusion...) receives a cart ID and will do something with that cart. A has to retrieve the cart using that ID, make some validations on the cart and, in case of error, return an ad hoc message (always as a string). This retrieval-and-validation code will be repeated in each message that has to retrieve a cart.
This is the proper code I finally got:
This is A: (Pay no attention to the #try message. It just assures that no exception gets out without being converted to a string. If anyone knows how to do this in a better way, please tell me how!)
add: aQuantity booksWithISBN: aBookISBN toCart: aCartId
| aCart aContext |
aContext := thisContext.
^self try: [
aCart := self retrieveCart: aCartId onErrorReturnFrom: aContext.
[aCart add: aQuantity booksWithISBN: aBookISBN]
on: Error
do: [ :anError | ^'1|', (self formatAsResponse: anError messageText) ].
^'0|OK'.
].
This is B:
retrieveCart: aCartId onErrorReturnFrom: aContext
| aCartCreationDateAndTime aCart |
[aCartCreationDateAndTime := cartCreationDateAndTime at: aCartId asInteger.]
on: KeyNotFound
do: [ aContext return: ('1|', (UserInterface invalidCartIdErrorMessage)).].
(systemClock now > (aCartCreationDateAndTime + 30 minutes))
ifTrue: [aContext return: ('1|', (UserInterface cartHasExpiredErrorMessage))].
[aCart := carts at: aCartId asInteger.]
on: KeyNotFound
do: [ aContext return: ('1|', (UserInterface invalidCartIdErrorMessage))].
^aCart.
A
| aResult aPartialResult |
aPartialResult := self B.
"do things with aPartialResult"
^aResult.
B
| aResult |
[ aResult := "do something" ]
on: Exception
do: ["make A return something"].
^aResult.
i would write it as
A
| aResult aPartialResult |
aPartialResult := self B.
aPartialResult ifNotNil:[
"do things with aPartialResult"].
^aResult.
B
| aResult |
[ aResult := "do something" ]
on: Exception
do: [:e|^nil].
^aResult.
well then thats just me!!!
Related
❯ jq --version
jq-1.6
I'm using .jq file as a filter like following, it works:
❯ cat jq/script.jq
def fi(v):
v | tostring |
if test("\\.") then
"float"
else
"integer"
end;
def estype(v):
if type=="number" then
fi(v)
else
type
end;
def esprop(v):
if type=="object" then
{"properties": v | with_entries(.value |= esprop(.))}
else
{"type": estype(v)}
end;
with_entries(.value |= esprop(.))
❯ cat test.json | jq -f jq/script.jq
...(omit results)
But when I use it as library, it throw an error:
# comment the last filter, except the definitions of functions
❯ cat jq/script.jq
def fi(v):
v | tostring |
if test("\\.") then
"float"
else
"integer"
end;
def estype(v):
if type=="number" then
fi(v)
else
type
end;
def esprop(v):
if type=="object" then
{"properties": v | with_entries(.value |= esprop(.))}
else
{"type": estype(v)}
end;
# with_entries(.value |= esprop(.))
❯ cat test.json | jq -L jq/script.jq 'import script;'
jq: error: syntax error, unexpected IDENT, expecting FORMAT or QQSTRING_START (Unix shell quoting issues?) at <top-level>, line 1:
import script;
jq: 1 compile error
What it means and how could I debug and fix this?
Are .jq files as a filter or a library has different syntax(doesn't seems like that)?
1a. What does it mean?
syntax error, unexpected IDENT, expecting FORMAT or QQSTRING_START
This means the parser found an identifier where it was expecting a string. (FORMAT is the token for a formatter like #csv or #text, while QQSTRING_START is the token for a string, like "script". In practice it's useless to use a formatter here since it won't let you use a non-constant string, but the parser doesn't know that.)
1b. How to debug and fix this?
Probably easiest to refer back to the manual. It says that the form expect for "import" is
import RelativePathString as NAME;
and the form expected for "include" is
include RelativePathString;
It lacks examples to make this 100% clear, but "RelativePathString" is a placeholder - it needs to be a literal string. Try one of these:
cat test.json | jq -L jq 'include "script"; with_entries(.value |= esprop(.))'
cat test.json | jq -L jq 'import "script" as script; with_entries(.value |= script::esprop(.))'
Note that the library path should be the directory containing your script, and the difference between include and import.
2. Do .jq files used as a filter or a library have a different syntax?
They use the same syntax. The problem was with the import statement, not with the script file.
The following code:
trait ClientResponse: DeserializeOwned + Send + Sized {}
struct ClientMsg {
...
resp: oneshot::Sender<Box<dyn ClientResponse>>
}
async fn client_thread(rx: mpsc::Receiver<ClientMsg>, client: reqwest::Client, base_url: Url) -> Result<(), Box<dyn Error>> {
while let Some(msg) = rx.recv().await {
...
let response = client.get(url).send().await?.json().await?;
msg.resp.send(response);
}
}
Fails with error:
error[E0038]: the trait `ClientResponse` cannot be made into an object
--> src/main.rs:16:11
|
16 | resp: oneshot::Sender<Box<dyn ClientResponse>>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `ClientResponse` cannot be made into an object
|
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
--> src/main.rs:12:23
|
12 | trait ClientResponse: DeserializeOwned + Send + Sized {}
| -------------- ^^^^^^^^^^^^^^^^ ^^^^^ ...because it requires `Self: Sized`
| | |
| | ...because it requires `Self: Sized`
| this trait cannot be made into an object...
As you can see, I tried adding the Sized trait as a super trait after reading the compiler error, but it still gives me the same error. I'm not sure how else to approach this problem, since I want a client thread that can deserialize responses into types decided by the senders.
There is a crate exactly for making serde traits object safe called erased-serde
I'm trying to run/script my existing rust async code with rlua-async. Sadly it is not well documented and has no examples but I have prevailed in getting my async functions defined but I have trouble getting my lua code executed in an async way.
I have created a minimal repository to reproduce the problem here
use rlua::{Lua};
use rlua_async::{ChunkExt, ContextExt};
#[actix_rt::main]
async fn main() {
let lua_code = "my.asyncfunc(42)";
let lua = Lua::new();
lua.context(|lua_ctx| {
let globals = lua_ctx.globals();
let map_table = lua_ctx.create_table().unwrap();
map_table
.set(
"asyncfunc",
lua_ctx
.create_async_function(
|_ctx,
param:
u32
| async move {
println!("async function called {}", param);
Ok(())
}).unwrap()).unwrap();
globals.set("my", map_table).unwrap();
});
lua.context(|lua_context| async move {
let chunk = lua_context
.load(&lua_code);
chunk.exec_async(lua_context).await.unwrap();
})
.await;
println!("finished");
}
But I'm getting this error message:
error: lifetime may not live long enough
--> src\main.rs:28:31
|
28 | lua.context(|lua_context| async move {
| __________________------------_^
| | | |
| | | return type of closure is impl Future
| | has type `LuaContext<'1>`
29 | | let chunk = lua_context
30 | | .load(&lua_code);
31 | | chunk.exec_async(lua_context).await.unwrap();
32 | | })
| |_____^ returning this value requires that `'1` must outlive `'2`
I really don't get what the error is trying to tell me and there is no helpful tips or even documentation linked.
The closure is somehow different from the closure body and needs lifetime annotations? But why and how...?
EDIT: if I instead call the code without async like this:
lua.context(|lua_context| {
let chunk = lua_context.load(&lua_code);
chunk.exec().unwrap();
});
it compiles but I get the following panic on runtime:
thread 'main' panicked at 'cannot access a scoped thread local variable without calling `set` first', C:\Users\ahallmann\.cargo\registry\src\github.com-1ecc6299db9ec823\scoped-tls-1.0.0\src\lib.rs:168:9
Everything works fine if I define the function with create_function.
I figured it out with the kind help of the author of rlua-async.
The issue is with the actix-rt itself as it requires 'static lifetimes for the block_on call.
It works fine if you use either futures or tokio instead:
tokio::runtime::Runtime::new()
.unwrap()
.block_on(chunk.exec_async(ctx))
See https://github.com/actix/actix-net/issues/201
or
https://github.com/Ekleog/rlua-async/issues/1 for further information.
Sorry if this is included somewhere, looked for a good 30-60 minutes for something along these lines. I am sure I just missed something! Total jq nub!
Basically I am trying to do a pick operation that is dynamic. My thought process was to do something like this:
pickJSON() {
getSomeJSON | jq -r --arg PICK "$1" '{ $PICK }'
}
pickJSON "foo, bar"
but this produces
{ "PICK": "foo, bar" }
Is there a way to essentially ask it to expand shell-style?
Desired Result:
pickJSON() {
getSomeJSON | jq -r --arg PICK "$1" '{ $PICK }'
# perhaps something like...
# getSomeJSON | jq -r --arg PICK "$1" '{ ...$PICK }'
}
pickJSON "foo, bar"
{ "foo": "foovalue", "bar": "barvalue" }
Note that I am new to jq and i just simplified what i am doing - if the syntax is broken that is why :-D my actual implementaiton has a few pipes in there and it does work if i dont try to pick the values out of it.
After a fairly long experimentation phase trying to make this work, I finally came up with what seems like a feasible and reliable solution without the extremely unsettling flaws that could come from utilizing eval.
To better highlight the overall final solution, I am providing a bit more of the handling that I am currently working with below:
Goal
Grab a secret from AWS Secrets Manager
Parse the returned JSON, which looks like this:
{
"ARN": "arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-a1b2c3",
"Name": "MyTestDatabaseSecret",
"VersionId": "EXAMPLE1-90ab-cdef-fedc-ba987EXAMPLE",
"SecretString": "{\n \"username\":\"david\",\n \"password\":\"BnQw&XDWgaEeT9XGTT29\"\n}\n",
"VersionStages": [
"AWSPREVIOUS"
],
"CreatedDate": 1523477145.713
}
Run some modifications on the JSON string received and pick only the statically requested keys from the secret
Set and export those values as environment variables
Script
# Capture a AWS Secret from secretsmanager, parse the JSON and expand the given
# variables within it to pick them from the secret and return given portion of
# the secret requested.
# #note similar to _.pick(obj, ["foo", "bar"])
getKeysFromSecret() {
aws secretsmanager get-secret-value --secret-id "$1" \
| jq -r '.SecretString | fromjson' \
| jq -r "{ $2 }"
}
# Uses `getKeysFromSecret` to capture the requested keys from the secret
# then transforms the JSON into a string that we can read and loop through
# to set each resulting value as an exported environment variable.
#
## Transformation Flow:
# { "foo": "bar", "baz": "qux" }
# -->
# foo=bar
# baz=qux
# -->
# export foo=bar
# export baz=qux
exportVariablesInSecret() {
while IFS== read -r key value; do
if [ -n "$value" ]; then
export "${key}"="${value}";
fi
done < <(getKeysFromSecret "$1" "$2" | jq -r 'to_entries | .[] | .key + "=" + .value')
}
Example JSON
{
...othervalues
"SecretString": "{\"foo\": \"bar\", \"baz\": \"qux\"}"
}
Example Usage
exportVariablesInSecret MY_SECRET "foo, bar"
echo $foo
# bar
Some Notes / Context
This is meant to set a given set of values as variables so that we aren't just setting an entire arbitrary JSON object as variables that could possibly cause issues / shadowing if someone adds a value like "path" to a secret
A critical goal was to absolutely never use eval to prevent possible injection situations. Far too easy to inject things otherwise.
Happy to see if anyone has a nicer way of accomplishing this. I saw many people recommending the use of declare but that sets the var to the local function scope only so its essentially useless.
Thanks to #cas https://unix.stackexchange.com/a/413917/308550 for getting me on the right track!
I am trying to use jq 1.5 to develop a script that can take one or more user inputs that represent a key and recursively remove them from JSON input.
The JSON I am referencing is here:
https://github.com/EmersonElectricCo/fsf/blob/master/docs/Test.json
My script, which seems to work pretty well, is as follows.
def post_recurse(f):
def r:
(f | select(. != null) | r), .;
r;
def post_recurse:
post_recurse(.[]?);
(post_recurse | objects) |= del(.META_BASIC_INFO)
However, I would like to replace META_BASIC_INFO with one or more user inputs. How would I go about accomplishing this? I presume with --arg from the command line, but I am unclear on how to incorporate this into my .jq script?
I've tried replacing del(.META_BASIC_INFO) with del(.$module) and invoking with cat test.json | ./jq -f fsf_key_filter.jq --arg module META_BASIC_INFO to test but this does not work.
Any guideance on this is greatly appreciated!
ANSWER:
Based on a couple of suggestions I was able to arrive to the following that works and users JQ.
Innvocation:
cat test.json | jq --argjson delete '["META_BASIC_INFO","SCAN_YARA"]' -f fsf_module_filter.jq
Code:
def post_recurse(f):
def r:
(f | select(. != null) | r), .;
r;
def post_recurse:
post_recurse(.[]?);
(post_recurse | objects) |= reduce $delete[] as $d (.; delpaths([[ $d ]]))
It seems the name module is a keyword in 1.5 so $module will result in a syntax error. You should use a different name. There are other builtins to do recursion for you, consider using them instead of churning out your own.
$ jq '(.. | objects | select(has($a))) |= del(.[$a])' --arg a "META_BASIC_INFO" Test.json
You could also use delpaths/1. For example:
$ jq -n '{"a":1, "b": 1} | delpaths([["a"]])'
{
"b": 1
}
That is, modifying your program so that the last line reads like this:
(post_recurse | objects) |= delpaths([[ $delete ]] )
you would invoke jq like so:
$ jq --arg delete "META_BASIC_INFO" -f delete.jq input.json
(One cannot use --arg module ... as "$module" has some kind of reserved status.)
Here's a "one-line" solution using walk/1:
jq --arg d "META_BASIC_INFO" 'walk(if type == "object" then del(.[$d]) else . end)' input.json
If walk/1 is not in your jq, here is its definition:
# Apply f to composite entities recursively, and to atoms
def walk(f):
. as $in
| if type == "object" then
reduce keys[] as $key
( {}; . + { ($key): ($in[$key] | walk(f)) } ) | f
elif type == "array" then map( walk(f) ) | f
else f
end;
If you want to recursively delete a bunch of key-value pairs, then here's one approach using --argjson:
rdelete.jq:
def rdelete(key):
walk(if type == "object" then del(.[key]) else . end);
reduce $strings[] as $s (.; rdelete($s))
Invocation:
$ jq --argjson strings '["a","b"]' -f rdelete.jq input.json