Skip to content

Commit

Permalink
feat(logql/streamSelector): add init support for Object (#159)
Browse files Browse the repository at this point in the history
* feat(logql/streamSelector): add init support for Object

* refactor: split initialization with new methods

* constructor if-else-if
  • Loading branch information
PierreDemailly authored Nov 23, 2023
1 parent 265d656 commit 7b78c71
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 10 deletions.
22 changes: 22 additions & 0 deletions src/logql/docs/StreamSelector.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,31 @@ Initializes a new instance of the `StreamSelector` class with an optional LogQL
Available `init` types are:
- `string` - Create an instance of `StreamSelector` given a **LogQL** query to parse.
- `string[]` - Create an instance of `StreamSelector` given multiple **LogQL** queries to parse.
- `Record<string, string | RegExp | StreamSelectorOp>` - Create an instance of `StreamSelector` given a key-value object. By default, the operator is an **exactlyEqual** (`=`) if value is `string` or a **Matches Regexp** (`=~`) if value is `RegExp`. `StreamSelectorOp` is returned by `StreamSelector.Equal()` & `StreamSelector.Not()`.
- `Iterable<[string, string]>` - Create an instance of `StreamSelector` given a `key`-`value` iterable. The default operator will be an **exactlyEqual** (`=`).
- `StreamSelector` - Create an instance of `StreamSelector` based on another `StreamSelector` class.

### `static Equal(value: string | RegExp)`

Utility static method that allow to init **exactlyEqual** (`=`) or **Matches Regexp** (`=~`) values via Object, depending weither value is `string` or `RegExp`.

```ts
const streamSelector = new StreamSelector({ foo: StreamSelector.Equal("bar") })
```

This is equal to:
```ts
const streamSelector = new StreamSelector({ foo: "bar" })
```

### `static Not(value: string | RegExp)`

Utility static method that allow to init **notEqual** (`!=`) or **Does not Match** (`!~`) values via Object.

```ts
const streamSelector = new StreamSelector({ foo: StreamSelector.Not("bar") })
```

### set(labelKey: string, labelValue: LabelValue | string, op?: LabelMatchingOperator)`

> [!IMPORTANT]
Expand Down
72 changes: 62 additions & 10 deletions src/logql/src/streamSelector.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// CONSTANTS
const kLabelMatchingOperators = ["=", "!=", "=~", "!~"];
const kEqual = Symbol("equal");
const kNotEqual = Symbol("notEqual");

export type LabelMatchingOperator = "=" | "!=" | "=~" | "!~";

Expand All @@ -9,27 +11,44 @@ export interface StreamSelectorValue {
}

export type LabelValue = Partial<StreamSelectorValue> & Pick<StreamSelectorValue, "value">;
export type StreamSelectorOp = { [kEqual]?: string | RegExp } | { [kNotEqual]?: string | RegExp };

export class StreamSelector extends Map<string, StreamSelectorValue> {
constructor(init?: string | string[] | Iterable<[string, string]> | StreamSelector) {
super();
static Equal(value: string | RegExp) {
return {
[kEqual]: value
};
}

if (init instanceof StreamSelector) {
this.#clone(init);
static Not(value: string | RegExp) {
return {
[kNotEqual]: value
};
}

return;
}
constructor(
init?: string | string[] | Record<string, string | RegExp | StreamSelectorOp> | Iterable<[string, string]> | StreamSelector
) {
super();

if (!init) {
return;
}

if (typeof init === "string") {
else if (init instanceof StreamSelector) {
this.#clone(init);
}
else if (typeof init === "string") {
this.#parse(init);

return;
}
else if (Symbol.iterator in init) {
this.#parseIterable(init);
}
else {
this.#parseObject(init);
}
}

#parseIterable(init: string[] | Iterable<[string, string]>) {
for (const query of init) {
if (typeof query === "string") {
this.#parse(query);
Expand All @@ -42,6 +61,39 @@ export class StreamSelector extends Map<string, StreamSelectorValue> {
}
}

#parseObject(init: Record<string, string | RegExp | StreamSelectorOp>) {
for (const [key, value] of Object.entries(init)) {
if (typeof value === "string") {
super.set(key, { value, operator: "=" });

continue;
}
else if (value instanceof RegExp) {
super.set(key, { value: value.source, operator: "=~" });

continue;
}

if (value[kEqual]) {
super.set(key, {
value: value[kEqual] instanceof RegExp ? value[kEqual].source : value[kEqual],
operator: value[kEqual] instanceof RegExp ? "=~" : "="
});

continue;
}

if (value[kNotEqual]) {
super.set(key, {
value: value[kNotEqual] instanceof RegExp ? value[kNotEqual].source : value[kNotEqual],
operator: value[kNotEqual] instanceof RegExp ? "!~" : "!="
});

continue;
}
}
}

#clone(streamSelector: StreamSelector) {
for (const [labelKey, labelValue] of streamSelector) {
this.set(labelKey, labelValue);
Expand Down
78 changes: 78 additions & 0 deletions src/logql/test/streamSelector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,48 @@ describe("StreamSelector", () => {
assert.strictEqual(streamSelector.get("app")!.value, "foo");
assert.strictEqual(streamSelector.get("env")!.value, "dev");
});

it("should handle an object with string value", () => {
const streamSelector = new StreamSelector({ foo: "bar" });

assert.strictEqual(streamSelector.get("foo")!.value, "bar");
assert.strictEqual(streamSelector.get("foo")!.operator, "=");
});

it("should handle an object with regexp value", () => {
const streamSelector = new StreamSelector({ foo: /^bar/ });

assert.strictEqual(streamSelector.get("foo")!.value, "^bar");
assert.strictEqual(streamSelector.get("foo")!.operator, "=~");
});

it("should handle an object with StreamSelector.Equal string value", () => {
const streamSelector = new StreamSelector({ foo: StreamSelector.Equal("bar") });

assert.strictEqual(streamSelector.get("foo")!.value, "bar");
assert.strictEqual(streamSelector.get("foo")!.operator, "=");
});

it("should handle an object with StreamSelector.Equal regexp value", () => {
const streamSelector = new StreamSelector({ foo: StreamSelector.Equal(/^bar/) });

assert.strictEqual(streamSelector.get("foo")!.value, "^bar");
assert.strictEqual(streamSelector.get("foo")!.operator, "=~");
});

it("should handle an object with StreamSelector.Not string value", () => {
const streamSelector = new StreamSelector({ foo: StreamSelector.Not("bar") });

assert.strictEqual(streamSelector.get("foo")!.value, "bar");
assert.strictEqual(streamSelector.get("foo")!.operator, "!=");
});

it("should handle an object with StreamSelector.Not regexp value", () => {
const streamSelector = new StreamSelector({ foo: StreamSelector.Not(/^bar/) });

assert.strictEqual(streamSelector.get("foo")!.value, "^bar");
assert.strictEqual(streamSelector.get("foo")!.operator, "!~");
});
});

describe("label matching operators", () => {
Expand Down Expand Up @@ -235,6 +277,42 @@ describe("StreamSelector", () => {

assert.strictEqual(streamSelector.toString(), "{foo=\"foo\",bar!=\"bar\",foz=~\"fo[oz]\",baz!~\"ba[rz]\"}");
});

it("should stringify a given object with string value", () => {
const streamSelector = new StreamSelector({ foo: "bar" });

assert.strictEqual(streamSelector.toString(), "{foo=\"bar\"}");
});

it("should stringify a given object with regexp value", () => {
const streamSelector = new StreamSelector({ foo: /^bar/ });

assert.strictEqual(streamSelector.toString(), "{foo=~\"^bar\"}");
});

it("should stringify a given object with StreamSelector.Equal string value", () => {
const streamSelector = new StreamSelector({ foo: StreamSelector.Equal("bar") });

assert.strictEqual(streamSelector.toString(), "{foo=\"bar\"}");
});

it("should stringify a given object with StreamSelector.Equal regexp value", () => {
const streamSelector = new StreamSelector({ foo: StreamSelector.Equal(/^bar/) });

assert.strictEqual(streamSelector.toString(), "{foo=~\"^bar\"}");
});

it("should stringify a given object with StreamSelector.Not string value", () => {
const streamSelector = new StreamSelector({ foo: StreamSelector.Not("bar") });

assert.strictEqual(streamSelector.toString(), "{foo!=\"bar\"}");
});

it("should stringify a given object with StreamSelector.Not regexp value", () => {
const streamSelector = new StreamSelector({ foo: StreamSelector.Not(/^bar/) });

assert.strictEqual(streamSelector.toString(), "{foo!~\"^bar\"}");
});
});

describe("toJSON()", () => {
Expand Down

0 comments on commit 7b78c71

Please sign in to comment.