Skip to content

Commit

Permalink
feat(url-state-provider): new way of encoding and decoding query string
Browse files Browse the repository at this point in the history
  • Loading branch information
taymoor89 committed Jan 10, 2025
1 parent d37dce4 commit 5f07139
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/light-vans-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudoperators/juno-url-state-provider": minor
---

Exports new `encodeV2` and `decodeV2` utilities to encode js object to url query parameters in a standard way as well as to decode query parameters back to javascript object.
53 changes: 52 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions packages/url-state-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,16 @@
"lz-string": "^1.4.4",
"typescript": "^5.5.4",
"vite": "^5.4.8",
"vitest": "^2.1.1",
"vite-plugin-dts": "^4.0.3"
"vite-plugin-dts": "^4.0.3",
"vitest": "^2.1.1"
},
"babel": {
"presets": [
"@babel/preset-env"
]
},
"dependencies": {
"juri": "^1.0.3"
"juri": "^1.0.3",
"query-string": "^9.1.1"
}
}
2 changes: 2 additions & 0 deletions packages/url-state-provider/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,5 @@ export {
decode,
encode,
}

export { encode as encodeV2, decode as decodeV2 } from "./v2"
13 changes: 13 additions & 0 deletions packages/url-state-provider/src/v2/decode.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import decode from "./decode"
import testCases from "./testCases"

describe("decode", () => {
it.each(testCases)("[$id] should successfully decode given input", ({ encoded: input, decoded: output }) => {
expect(decode(input)).toMatchObject(output)
})
})
16 changes: 16 additions & 0 deletions packages/url-state-provider/src/v2/decode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import queryString from "query-string"
import { DecodedObject } from "./types"

const decode = (string: string): DecodedObject =>
queryString.parse(string, {
arrayFormat: "comma",
parseBooleans: true,
parseNumbers: true,
})

export default decode
28 changes: 28 additions & 0 deletions packages/url-state-provider/src/v2/encode.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import encode from "./encode"
import testCases from "./testCases"

describe("encode", () => {
it.each(testCases)("[$id] should successfully encode given input", ({ decoded: input, encoded: output }) => {
expect(encode(input)).toBe(output)
})

it.each`
description | input
${"not an object"} | ${null}
${"not an object"} | ${undefined}
${"not an object"} | ${true}
${"not an object"} | ${false}
${"not an object"} | ${1}
${"not an object"} | ${"string"}
${"not an object"} | ${/regexp/}
${"an array"} | ${[1, 2, 3]}
${"a valid object but the value of each key does not conform to required type"} | ${{ a: "b", c: { d: "e" } }}
`("should throw an error when input is $description", ({ input }) => {
expect(() => encode(input)).toThrowError("Invalid object to encode")
})
})
49 changes: 49 additions & 0 deletions packages/url-state-provider/src/v2/encode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import queryString from "query-string"
import { ObjectToEncode, Primitive } from "./types"

const isPrimitive = (value: Primitive | Primitive[]) => {
return (
typeof value === "string" ||
typeof value === "number" ||
typeof value === "boolean" ||
value === null ||
value === undefined ||
value instanceof RegExp
)
}

const validateObjectToEncode = (object: ObjectToEncode) => {
if (object === null || typeof object !== "object" || object instanceof RegExp || Array.isArray(object)) {
return false
}

for (const key in object) {
const value = object[key]
if (!isPrimitive(value) && !Array.isArray(value)) {
return false
}
if (Array.isArray(value) && !value.every(isPrimitive)) {
return false
}
}

return true
}

const encode = (object: ObjectToEncode) => {
if (!validateObjectToEncode(object)) {
throw new TypeError(`Invalid object to encode`)
}

return queryString.stringify(object, {
arrayFormat: "comma",
sort: false,
})
}

export default encode
9 changes: 9 additions & 0 deletions packages/url-state-provider/src/v2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import encode from "./encode"
import decode from "./decode"

export { encode, decode }
35 changes: 35 additions & 0 deletions packages/url-state-provider/src/v2/testCases.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { DecodedObject } from "./types"

type TestCase = {
id: number
encoded: string
decoded: DecodedObject
}

const testCases: TestCase[] = [
{
encoded: "aString=someId1&aNumber=2&aBoolean=true&anArray=1,true,something",
decoded: { aString: "someId1", aNumber: 2, aBoolean: true, anArray: [1, true, "something"] },
},
{
encoded: "aStringWithSpaces=some%20id%20and",
decoded: { aStringWithSpaces: "some id and" },
},
{
encoded: "regularExpression=%2Fw3schools%2Fi",
decoded: { regularExpression: /w3schools/i },
},
{
encoded: "aStringWithSpecialCharacter=A%26B",
decoded: { aStringWithSpecialCharacter: "A&B" },
},
]
//assign 'id' to each test case to better identify which one is failing
.map((item, idx) => ({ id: idx + 1, ...item }))

export default testCases
13 changes: 13 additions & 0 deletions packages/url-state-provider/src/v2/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

export type Primitive = string | number | boolean | null | undefined | RegExp

type LimitedNestedObject = {
[key: string]: Primitive | Primitive[]
}

export type ObjectToEncode = LimitedNestedObject
export type DecodedObject = LimitedNestedObject

0 comments on commit 5f07139

Please sign in to comment.