Here is the classic situation :
macro_rules! foo {
($a:tt $(b:tt)?) => {
do_something_with_a($a);
$(do_something_with_b($b);)?
}
}
But what if you want this:
macro_rules! foo {
($a:tt) => {
do_something_with_a($a);
do_something_when_b_is_absent();
}
($a:tt b:tt) => {
do_something_with_a($a);
do_something_with_b($b);
}
}
How to avoid code repetition here ?
Is there a way to do something like:
macro_rules! foo {
($a:tt $(b:tt)?) => {
do_something_with_a($a);
$(do_something_with_b($b);)?
${if(
eq(count(b), 0),
{do_something_when_b_is_absent();}
)}
}
}
My guess is no, but I could be surprised :)
You could do this using #internal to differentiate between the 2 cases,
or use a different macro for the other cases:
macro_rules! foo {
(#internal ) => {
do_something_when_b_is_absent();
};
(#internal $b:tt) => {
do_something_with_b($b);
};
($a:tt $($b:tt)?) => {
do_something_with_a($a);
foo!(#internal $($b)?);
};
}
It turns out, it is possible to write a macro that does the magic:
macro_rules! if_not {
(() $code:block) => {
$code
};
(($($target:tt)+) $code:block) => {};
}
Then you use it this way:
macro_rules! foo {
($a:tt $(b:tt)?) => {
do_something_with_a($a);
$(do_something_with_b($b);)?
if_not!(($(b)?) {
do_something_when_b_is_absent();
});
}
}
Related
I'm trying to create a Rust macro that generates endpoints like "/api/v2/stats/<token>".
I thought it would be cool to make it like warp path does it: warp::path!("sum" / u32 / u32).
But in warp, they do not need to support token trees with dots, i.e. expressions, and retrieve their values...
What I got so far is this:
macro_rules! path {
() => {};
($next:tt $($tail:tt)*) => {{
println!(stringify!($next));
path!($($tail)*);
}};
}
fn main() {
struct Data {
event: String,
token: String,
}
let data = Data {
event: String::from("stats"),
token: String::from("a1b2c3d4"),
};
path!("/api/v2" / data.event / data.token)
}
playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=babf87265cc6060fc1695019e30e38bf
This shows what the macro is seeing:
"/api/v2"
/
data
.
event
/
data
.
token
I know token trees can be reinterpreted as expressions later, so there should be a way to keep tt's in tail, split slashes from "anything else", and get those as expressions to retrieve their values, but I'm not seeing how...
How could I make it return the String "/api/v2/stats/a1b2c3d4"?
More examples of inputs and expected outputs:
struct Conf<'a> {env: &'a str};
let conf = Conf { env: "dev" };
let subsystem = "stats";
path!("/"); // root: "/"
path!("/api/v1" / data.event / "results"); // "/api/v1/stats/results"
path!("/api/v2/errors" / conf.env / subsystem); // "/api/v2/errors/dev/stats"
EDIT: I kinda did it with expressions, which it not that expressive, more of a workaround, but it works:
macro_rules! path {
($($path:expr),+) => {{
let mut s = [$($path),+].into_iter().flat_map(|p| [p, "/"]).collect::<String>();
s.pop();
s
}}
}
Limitations: it only accepts commas, the path parts have to be &str so I have to manually reference them, and most of all, this could be better represented as a function, but is something to start with:
let result_url = path!("/api/v2", &data.event, &data.token);
Thank you!
You can use a tt muncher to achieve this:
macro_rules! path {
(#munch / ) => {
String::from("/")
};
(#munch / $part:literal $(/)* ) => {
format!("/{}", $part)
};
(#munch / $part:literal / $($tail:tt)* ) => {
format!("/{}{}", $part, path!(#munch / $($tail)*))
};
(#munch / $($parts:ident).+ $(/)* ) => {
format!("/{}", & $($parts).+)
};
(#munch / $($parts:ident).+ / $($tail:tt)* ) => {
format!("/{}{}", & $($parts).+, path!(#munch / $($tail)*))
};
(/ $($input:tt)*) => {
path!(#munch / $($input)*)
};
}
playground
Currently this produces nested format! calls. In order to avoid that you'll probably also need to use an accumulator. This kinda stuff interests me so I'm working on a version with an accumulator.
Edit: And here's the accumulator version
macro_rules! path {
(/) => {
String::from("/")
};
(/ $($input:tt)*) => {
path!(#munch { / $($input)* } => ())
};
(#munch { / $part:literal $(/)* } => ($($accum:expr),*)) => {
path!(#done ($( $accum, )* $part))
};
(#munch { / $part:literal / $($tail:tt)* } => ($($accum:expr),*)) => {
path!(#munch { / $($tail)* } => ($( $accum, )* $part ))
};
(#munch { / $($parts:ident).+ $(/)* } => ($($accum:expr),*)) => {
path!(#done ($( $accum, )* & $($parts).+ ))
};
(#munch { / $($parts:ident).+ / $($tail:tt)* } => ($($accum:expr),*)) => {
path!(#munch { / $($tail)* } => ($( $accum, )* & $($parts).+ ))
};
(#replace_expr $_t:tt => $sub:expr) => { $sub };
(#done ($($accum:expr),*)) => {
format!(
concat!($( path!(#replace_expr ($accum) => "/{}"), )*),
$( $accum, )*
)
};
}
playground
Edit2: per your request, another version which uses two accumulators to support a leading literal
macro_rules! path {
(/) => {
String::from("/")
};
(/ $($input:tt)*) => {
path!(#munch { / $($input)* } -> () : ())
};
($part:literal $(/)*) => {
String::from($part)
};
($part:literal $($input:tt)*) => {
path!(#munch { $($input)* } -> ("{}") : ($part))
};
(#munch { / $part:literal $(/)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
path!(#done ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* $part))
};
(#munch { / $part:literal / $($tail:tt)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
path!(#munch { / $($tail)* } -> ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* $part ))
};
(#munch { / $($parts:ident).+ $(/)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
path!(#done ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* & $($parts).+ ))
};
(#munch { / $($parts:ident).+ / $($tail:tt)* } -> ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
path!(#munch { / $($tail)* } -> ($( $fmt_accum, )* "/{}") : ($( $args_accum, )* & $($parts).+ ))
};
(#done ($($fmt_accum:literal),*) : ($($args_accum:expr),*)) => {
format!(
concat!($( $fmt_accum, )*),
$( $args_accum, )*
)
};
}
playground
Currently, my code looks like this:
interface TestInterface<T extends string> {
data: {
[key in T]: () => void;
}
}
const testCommand: TestInterface<"fun1"> = {
data: {
fun1: () => {
// do something
},
}
};
testCommand.data.fun1();
I pass a string (in this example "fun1") as a generic to the testCommand function. In the data object, I can access this string/function "fun1".
My Question: Can I pass an array of strings as a generic to have a custom amount of functions in the data object (Maybe convert the data obj to an array)?
Example:
const testCommand: TestInterface<["fun1", "fun2", "fun3"]> = {
data: {
fun1: () => {
// do something
},
fun2: () => {
// do something
},
fun3: () => {
// do something
},
}
};
or:
const testCommand: TestInterface<["fun1", "fun2", "fun3"]> = {
data: [
{ name: "fun1", exec: () => {} },
{ name: "fun2", exec: () => {} },
{ name: "fun3", exec: () => {} },
]
};
Instead of an array, you can just pass in a union of the string literal types you want to support; the resulting mapped type will contain all of the keys:
const testCommand: TestInterface<"fun1" | "fun2" | "fun3"> = {
data: {
fun1: () => {
// do something
},
fun2: () => {
// do something
},
fun3: () => {
// do something
},
}
};
Indeed, this is very much like using the Record<K, V> utility type, where you pass in a union of key types for K and a value type for V, and get a type with properties of type V at all keys in K.
Playground link to code
Problem: I'm attempting to retrieve information from Firebase with a second function which relies on data that was set from a prior function call. However, the second function executes before the first has set the data. I know this is because of how functions execute in Typescript / Angular, but i'm not very familiar working with asynchronous functions.
Question: What is the best way to make the second function wait until the data it needs is set?
Additional Info: The data is being stored / retrieved from Firebase Firestore. The collection im working with contains any number of city documents. Each of these city documents contains a collection of families. Since the number of cities can vary, I need to first retrieve a list of cities that have families, then use that list to retrieve the families inside. I've attempted to use Promises to fix the issue, tried making the functions asynchronous (then using await), and making callback functions, but have had no luck. I've included my attempted solutions below. I there is any more code that I need to include (or if I need to post the Firestore layout) please let me know.
I'm also open to other solutions in retrieving / storing the data as long as it follows the same format as the Firestore data.
Code:
home.component.ts:
export class HomeComponent implements OnInit {
activeCities: any = [];
activeFamilies: Map<string, Family[]>;
constructor(private fbService: FirebaseService) { }
ngOnInit() {
this.getActiveCities();
this.getAllFamilies();
}
getActiveCities() {
this.fbService.getActiveCities().subscribe(
data => {
this.activeCities = data;
},
error => {
console.log("Error retrieving active cities");
}
);
}
getAllFamilies() {
for (let city of this.activeCities) {
this.fbService.getFamiliesForCity(city.id).subscribe(
data => {
let families: Family[] = [];
families = data;
this.activeFamilies.set(city .id, families);
},
error => {
console.log("Error retrieving families for active cities");
}
);
}
}
}
firebase.service.ts:
export class FirebaseService {
private activeCitiesPath = '/active_cities';
constructor(private firestore: AngularFirestore) { }
getActiveCities() {
let colRef: AngularFirestoreCollection<any>;
let temp: Observable<any[]>;
let path = this.activeCitiesPath;
colRef = this.firestore.collection(path);
return colRef.snapshotChanges().pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data() as any;
const id = a.payload.doc.id;
return { id, ...data };
}))
);
}
getFamiliesForCity(cityCode: string) {
let colRef: AngularFirestoreCollection<any>;
let temp: Observable<any[]>;
let path = this.activeCitiesPath + "/" + cityCode + "/families";
colRef = this.firestore.collection(path);
return colRef.snapshotChanges().pipe(
map(actions => actions.map(a => {
const data = a.payload.doc.data() as any;
const id = a.payload.doc.id;
return { id, ...data };
}))
);
}
}
Attempted solutions: I've tried the following solutions but neither have worked thus far:
With promises:
async ngOnInit() {
let promises: Promise<void>[] = [];
promises.push(this.getActiveCities());
promises.push(this.getAllFamilies());
Promise.all(promises).then(() => {
console.log("All promises worked");
}).catch(() => {
console.log("Error in promise");
});
}
private getActiveCities(): Promise<void> {
return new Promise<void>((resolve, reject) => {
//same code but adding resolve(); and reject();
});
}
private getAllFamilies(): Promise<void> {
return new Promise<void>((resolve, reject) => {
//same code but adding resolve(); and reject();
});
}
With asynchronous:
async ngOnInit() {
await this.getActiveCities();
await this.getAllFamilies();
}
With callbacks I attempted something similar to: https://stackoverflow.com/a/21518470/5785332
I've also tried to implement solutions from answers to similar question: async/await in Angular `ngOnInit`
Easiest solution is to just call your second function inside your first subscription:
ngOnInit() {
this.getActiveCities();
}
getActiveCities() {
this.fbService.getActiveCities().subscribe(
data => {
this.activeCities = data;
this.getAllFamilies();
},
error => {
console.log("Error retrieving active cities");
}
);
}
Although a better design is to keep everything as observables and subscribe with the async pipe in html.
export class HomeComponent implements OnInit {
constructor(private fbService: FirebaseService) { }
activeFamiliesMap = new Map<string, Observable<Family[]>>();
activeCities$: Observable<any[]> = this.fbService.getActiveCities().pipe(
tap((activeCities) => {
for (const city of activeCities) {
this.activeFamiliesMap.set(city.id, this.activeFamilies(city.id));
}
}),
catchError((err) => {
console.error('Error retrieving active cities', err);
return of([]);
})
);
activeFamilies(id: any): Observable<Family[]> {
return this.fbService.getFamiliesForCity(id).pipe(
catchError((err) => {
console.error('Error retrieving families for city id:', id, err);
return of([]);
})
);
}
}
Just an example of how to display the data:
<div>Active Cities</div>
<pre>{{ activeCities$ | async | json }}</pre>
<ng-container *ngFor="let city of activeCities$ | async">
<div>City Id: {{ city.id }}</div>
<div>Families:</div>
<pre>{{ activeFamiliesMap.get(city.id) | async | json }}</pre>
</ng-container>
stackblitz: https://stackblitz.com/edit/angular-ivy-trkuqx?file=src/app/app.component.ts
And an even better design may be for your service to return an observable of the map, although it's an ugly beast of an observable. At least it hides the logic from your components:
Service
getFamilyMap(): Observable<Map<string, Family[]>> {
return this.getActiveCities().pipe(
map((activeCities) => {
return activeCities.map((city) => {
return this.getFamiliesForCity(city.id).pipe(
map((families) => {
return { id: city.id, families };
})
);
});
}),
switchMap((data) => forkJoin(data)),
map((data) => {
const res = new Map<string, Family[]>();
for (const entry of data) {
res.set(entry.id, entry.families);
}
return res;
}),
catchError((err) => {
console.error('Error retrieving family map', err);
return of(new Map<string, Family[]>());
})
);
}
Component
export class HomeComponent {
constructor(private fbService: FirebaseService) {}
activeCities$ = this.fbService.getActiveCities();
familyMapEntries$ = this.fbService
.getFamilyMap()
.pipe(map((map) => Array.from(map)));
}
I use Array.from() rather than map.entries() because iterators tend to throw changedAfterChecked errors.
Html
<div>Active Cities</div>
<pre>{{ activeCities$ | async | json }}</pre>
<ng-container *ngFor="let entry of (familyMapEntries$ | async)">
<div>City Id: {{ entry[0] }}</div>
<div>Families:</div>
<pre>{{ entry[1] | json }}</pre>
</ng-container>
stackblitz: https://stackblitz.com/edit/angular-ivy-ffzpya?file=src/app/firebase.service.ts
I cannot speak to the best angular design here. However to implement this with promises, the following should work. I went ahead and made both methods awaitable.
export class HomeComponent implements OnInit {
activeCities: any = [];
activeFamilies = new Map<string, Family[]>();
constructor(private fbService: FirebaseService) {}
ngOnInit() {
this.getActiveCities().then(() => {
this.getAllFamilies();
});
}
getActiveCities() {
return new Promise<void>((resolve, reject) => {
this.fbService.getActiveCities().subscribe(
(data) => {
this.activeCities = data;
resolve();
},
(error) => {
console.log('Error retrieving active cities');
reject(error);
}
);
});
}
getAllFamilies() {
const promises = Array<Promise<void>>();
for (let city of this.activeCities) {
promises.push(
new Promise<void>((resolve, reject) => {
this.fbService.getFamiliesForCity(city.id).subscribe(
(data) => {
this.activeFamilies.set(city.id, data);
resolve();
},
(error) => {
console.log('Error retrieving families for active cities');
reject(error);
}
);
})
);
}
return Promise.all(promises);
}
}
interface OnInit {
ngOnInit(): void;
}
interface Family {}
interface FirebaseService {
getActiveCities: () => {
subscribe: {
(success: (data: any[]) => void, error: (error?: any) => void): void;
};
};
getFamiliesForCity: (cityCode: string) => {
subscribe: {
(success: (data: any[]) => void, error: (error?: any) => void): void;
};
};
}
I'm not sure if you can make ngOnInit async either without causing problems but it then becomes.
async ngOnInit() {
await this.getActiveCities();
await this.getAllFamilies();
}
Here is an easy solution using rxjs lastValueFrom.
lastvalueFrom: Converts an observable to a promise by subscribing to the observable, waiting for it to complete, and resolving the returned promise with the last value from the observed stream.
This replaced the .toPromise() method. I was always a fan of converting my httpClient subscription's to promises, since your not really subscribing to a RESTful endpoint.
You can even throw a .catch() after, since lastValueFrom returns a promise! That is also the reason you get to use await ;)
Hopefully this helps!
import { lastValueFrom } from 'rxjs';
async ngOnInit(){
this.activeCities = await lastValueFrom(this.fbService.getActiveCities()).catch(err => {});
for (let city of this.activeCities) {
var data = await lastValueFrom(this.fbService.getFamiliesForCity(city.id));
let families: Family[] = [];
families = data;
this.activeFamilies.set(city .id, families);
}
}
Docs:
https://rxjs.dev/api/index/function/lastValueFrom
Consider this setup.
interface C {
foo: string
}
interface A {
B: Record<string, C>
// ... some more properties
}
const test: A = {
B: { hi: {foo: "hello"} }
// ... some more properties
}
now I want to be able to do
test.B.toArray()
that would do
Object.values(test.B)
Now here's a partial solution I came up with that I'm not satisfied with.
interface C {
foo: string;
}
interface A {
B: { value: Record<string, C>; toArray: () => Array<C> };
}
const test: A = {
B: {
value: { hi: { foo: "hello" } },
toArray: function (): Array<C> {
return Object.values(this.value);
},
},
};
//Why I am not happy with this solution is I now have to refer to
test.B
// as
test.B.value
By using the & you will be able to say that you want the interface also implement a function.
interface A {
B: Record<string, C & { toArray: () => Array<C> }>
// ... some more properties
}
I try to call function from same class but it always return an error TypeError: this.c is not a function I tried also module.exports.c() and the same result
module.exports = (options)=>{
return{
a:(a)=>{
console.log(a);
},
b:(b)=>{
this.c('c');
console.log(b)
},
c:(c)=>{
console.log(c);
}
}
}
After Updated
module.exports = ({})=>{
return{
genereate:function(identifier){
console.log('genereate')
},
middleware:function(req,res,next){
this.c();
console.log('genereate')
},
c:function(){
console.log('geeet');
}
}
}
Arrow functions bind this lexically (meaning it does not bind it's own this).
Use normal function expressions instead:
module.exports = (options) => {
return {
a: function(a){
console.log(a);
},
b: function(b){
this.c('c');
console.log(b)
},
c: function(c){
console.log(c);
}
};
};
Browserfied example:
let f = (options) => {
return {
a: function(a){
console.log(a);
},
b: function(b){
this.c('c');
console.log(b)
},
c: function(c){
console.log(c);
}
};
};
f().a("a");
f().b("b");
f().c("c");
You can try and export a class, just pass options to your constructor
class InnerCall {
constructor(options) {
this.options = options;
}
a(a) {
console.log(a);
}
b(b) {
this.c('c');
console.log(b);
}
c(c) {
console.log(c);
}
}
const example = new InnerCall({ option: 'Im an option' });
example.b('check this out');
console.log(example.options.option);