Skip to content

Commit

Permalink
escape special chars
Browse files Browse the repository at this point in the history
  • Loading branch information
anthonyjoeseph committed Mar 29, 2022
1 parent 8dd0b52 commit 0dc0392
Show file tree
Hide file tree
Showing 19 changed files with 307 additions and 90 deletions.
12 changes: 5 additions & 7 deletions src/types/get.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import type { Some } from "fp-ts/Option";
import type { Option } from "fp-ts/Option";
import type { AtPath } from "../util/AtPath";
import type { Build } from "../util/Build";
import type { Paths } from "../util/Paths";
import type { GiveOpt, HasOptional } from "../util/predicates";
import { AddDots } from "../util/segments";
import { AddNullSegments } from "../util/segments";

export type Get = <
Infer,
Path extends unknown extends Infer ? string : Paths<Infer>,
Ret extends unknown extends Infer ? unknown : GiveOpt<AtPath<Infer, AddDots<Path>>, AddDots<Path>>
Ret extends unknown extends Infer ? unknown : GiveOpt<AtPath<Infer, AddNullSegments<Path>>, AddNullSegments<Path>>
>(
path: Path & string
) => unknown extends Infer
? unknown extends Ret
? <Constructed extends Build<Path, unknown>>(
obj: Constructed
) => GiveOpt<AtPath<Constructed, AddDots<Path>>, AddDots<Path>>
) => GiveOpt<AtPath<Constructed, AddNullSegments<Path>>, AddNullSegments<Path>>
: true extends HasOptional<Path>
? (obj: Build<Path, OptionType<Ret>>) => Ret
? (obj: Build<Path, [Ret] extends [Option<infer A>] ? A : unknown>) => Ret
: (obj: Build<Path, Ret>) => Ret
: (obj: Infer) => Ret;

type OptionType<S> = Extract<S, Some<unknown>>["value"];
6 changes: 4 additions & 2 deletions src/types/modify.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { Paths } from "../util/Paths";
import type { AtPath } from "../util/AtPath";
import { AddDots } from "../util/segments";
import { AddNullSegments } from "../util/segments";

export type Modify = <Infer, Path extends Paths<Infer>>(
path: Path & string,
modFunc: (v: AtPath<Infer, AddDots<Path>, "no-traversals">) => AtPath<Infer, AddDots<Path>, "no-traversals">
modFunc: (
v: AtPath<Infer, AddNullSegments<Path>, "no-traversals">
) => AtPath<Infer, AddNullSegments<Path>, "no-traversals">
) => (a: Infer) => Infer;
14 changes: 9 additions & 5 deletions src/types/modifyF.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { Applicative, Applicative1, Applicative2, Applicative3 } from "fp-ts/lib
import { HKT, Kind, Kind2, Kind3, URIS, URIS2, URIS3 } from "fp-ts/lib/HKT";
import type { Paths } from "../util/Paths";
import type { AtPath } from "../util/AtPath";
import { AddDots } from "../util/segments";
import { AddNullSegments } from "../util/segments";

export type ModifyF = {
<F extends URIS3>(F: Applicative3<F>): <
R,
E,
Infer,
Path extends Paths<Infer>,
Val extends AtPath<Infer, AddDots<Path>, "no-traversals">
Val extends AtPath<Infer, AddNullSegments<Path>, "no-traversals">
>(
path: Path & string,
modFunc: (v: Val) => Kind3<F, R, E, Val>
Expand All @@ -19,20 +19,24 @@ export type ModifyF = {
E,
Infer,
Path extends Paths<Infer>,
Val extends AtPath<Infer, AddDots<Path>, "no-traversals">
Val extends AtPath<Infer, AddNullSegments<Path>, "no-traversals">
>(
path: Path & string,
modFunc: (v: Val) => Kind2<F, E, Val>
) => (a: Infer) => Kind2<F, E, Infer>;
<F extends URIS>(F: Applicative1<F>): <
Infer,
Path extends Paths<Infer>,
Val extends AtPath<Infer, AddDots<Path>, "no-traversals">
Val extends AtPath<Infer, AddNullSegments<Path>, "no-traversals">
>(
path: Path & string,
modFunc: (v: Val) => Kind<F, Val>
) => (a: Infer) => Kind<F, Infer>;
<F>(F: Applicative<F>): <Infer, Path extends Paths<Infer>, Val extends AtPath<Infer, AddDots<Path>, "no-traversals">>(
<F>(F: Applicative<F>): <
Infer,
Path extends Paths<Infer>,
Val extends AtPath<Infer, AddNullSegments<Path>, "no-traversals">
>(
path: Path & string,
modFunc: (v: Val) => HKT<F, Val>
) => (a: Infer) => HKT<F, Infer>;
Expand Down
8 changes: 5 additions & 3 deletions src/types/modifyOption.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { AtPath } from "../util/AtPath";
import type { Paths } from "../util/Paths";
import type { GiveOpt } from "../util/predicates";
import { AddDots } from "../util/segments";
import { AddNullSegments } from "../util/segments";

export type ModifyOption = <Infer, Path extends Paths<Infer>>(
path: Path & string,
modFunc: (v: AtPath<Infer, AddDots<Path>, "no-traversals">) => AtPath<Infer, AddDots<Path>, "no-traversals">
) => (a: Infer) => GiveOpt<Infer, AddDots<Path>>;
modFunc: (
v: AtPath<Infer, AddNullSegments<Path>, "no-traversals">
) => AtPath<Infer, AddNullSegments<Path>, "no-traversals">
) => (a: Infer) => GiveOpt<Infer, AddNullSegments<Path>>;
4 changes: 2 additions & 2 deletions src/types/modifyOptionW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import type { Paths } from "../util/Paths";
import type { Build } from "../util/Build";
import type { ApplyTraversals, AtPath } from "../util/AtPath";
import type { GiveOpt } from "../util/predicates";
import { AddDots } from "../util/segments";
import { AddNullSegments } from "../util/segments";

export type ModifyOptionW = <Infer, Path extends Paths<Infer>, RetVal>(
path: Path & string,
modFunc: (v: AtPath<Infer, AddDots<Path>, "no-traversals">) => RetVal
modFunc: (v: AtPath<Infer, AddNullSegments<Path>, "no-traversals">) => RetVal
) => (a: Infer) => GiveOpt<ApplyTraversals<Build<Path, RetVal, Infer>, Path>, Path>;
8 changes: 4 additions & 4 deletions src/types/modifyW.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import type { Paths } from "../util/Paths";
import type { Build } from "../util/Build";
import type { ApplyTraversals, AtPath } from "../util/AtPath";
import type { HasOptional } from "../util/predicates";
import { AddDots } from "../util/segments";
import { AddNullSegments } from "../util/segments";

export type ModifyW = <Infer, Path extends Paths<Infer>, RetVal>(
path: Path & string,
modFunc: (v: AtPath<Infer, AddDots<Path>, "no-traversals">) => RetVal
modFunc: (v: AtPath<Infer, AddNullSegments<Path>, "no-traversals">) => RetVal
) => (
a: Infer
) => true extends HasOptional<Path>
? ApplyTraversals<Build<Path, RetVal | AtPath<Infer, AddDots<Path>>, Infer>, AddDots<Path>>
: ApplyTraversals<Build<Path, RetVal, Infer>, AddDots<Path>>;
? ApplyTraversals<Build<Path, RetVal | AtPath<Infer, AddNullSegments<Path>>, Infer>, AddNullSegments<Path>>
: ApplyTraversals<Build<Path, RetVal, Infer>, AddNullSegments<Path>>;
4 changes: 2 additions & 2 deletions src/types/rename.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { Paths } from "../util/Paths";
import type { Build } from "../util/Build";
import { AtPath } from "../util/AtPath";
import { AddDots } from "../util/segments";
import { AddNullSegments } from "../util/segments";

export type Rename = <Infer, Path extends Paths<Infer, "dynamic">, NewKey extends string>(
fullPath: Path & string,
newKey: NewKey
) => (a: Infer) => Build<Path, AtPath<Infer, AddDots<Path>>, Infer, NewKey>;
) => (a: Infer) => Build<Path, AtPath<Infer, AddNullSegments<Path>>, Infer, NewKey>;
4 changes: 2 additions & 2 deletions src/types/set.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { AtPath } from "../util/AtPath";
import type { Paths } from "../util/Paths";
import { AddDots } from "../util/segments";
import { AddNullSegments } from "../util/segments";

export type Set = <Infer, Path extends Paths<Infer>, Val extends AtPath<Infer, AddDots<Path>, "no-traversals">>(
export type Set = <Infer, Path extends Paths<Infer>, Val extends AtPath<Infer, AddNullSegments<Path>, "no-traversals">>(
path: Path & string,
val: Val
) => (obj: Infer) => Infer;
8 changes: 6 additions & 2 deletions src/types/setOption.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import type { AtPath } from "../util/AtPath";
import type { Paths } from "../util/Paths";
import type { GiveOpt } from "../util/predicates";
import { AddDots } from "../util/segments";
import { AddNullSegments } from "../util/segments";

export type SetOption = <Infer, Path extends Paths<Infer>, Val extends AtPath<Infer, AddDots<Path>, "no-traversals">>(
export type SetOption = <
Infer,
Path extends Paths<Infer>,
Val extends AtPath<Infer, AddNullSegments<Path>, "no-traversals">
>(
path: Path & string,
val: Val
) => (obj: Infer) => GiveOpt<Infer, Path>;
29 changes: 17 additions & 12 deletions src/util/AtPath.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Some } from "fp-ts/Option";
import { Left, Right } from "fp-ts/Either";
import { FirstSegment, TailSegment, UnescapeParenthesis } from "./segments";

type Operation = "apply-traversals" | "no-traversals";

Expand All @@ -9,24 +10,28 @@ export type AtPath<A, Args extends string, Op extends Operation = "apply-travers
? A
: Op extends "apply-traversals"
? ApplyTraversals<
Args extends `${infer Key}.${infer Rest}`
? AtPath<ApplySegment<A, Key>, Rest, "no-traversals">
: ApplySegment<A, Args>,
TailSegment<Args> extends ""
? ApplySegment<A, Args>
: AtPath<ApplySegment<A, FirstSegment<Args>>, TailSegment<Args>, "no-traversals">,
Args
>
: Args extends `${infer Key}.${infer Rest}`
? AtPath<ApplySegment<A, Key>, Rest, Op>
: ApplySegment<A, Args>;
: TailSegment<Args> extends ""
? ApplySegment<A, Args>
: AtPath<ApplySegment<A, FirstSegment<Args>>, TailSegment<Args>, Op>;

export type ApplyTraversals<A, Args extends string> = Args extends ""
? A
: Args extends `${string}[]>${infer Tail}`
? ApplyTraversals<A[], Tail>
: Args extends `${string}{}>${infer Tail}`
? ApplyTraversals<Record<string, A>, Tail>
: A;
: FirstSegment<Args> extends `(${string}`
? ApplyTraversals<A, TailSegment<Args>>
: FirstSegment<Args> extends "[]>"
? ApplyTraversals<A[], TailSegment<Args>>
: FirstSegment<Args> extends "{}>"
? ApplyTraversals<Record<string, A>, TailSegment<Args>>
: ApplyTraversals<A, TailSegment<Args>>;

type ApplySegment<A, Seg extends string> = Seg extends "?"
type ApplySegment<A, Seg extends string> = Seg extends `(${string}`
? A[Extract<UnescapeParenthesis<Seg>, keyof A>]
: Seg extends "?"
? NonNullable<A>
: Seg extends "?some"
? Extract<A, Some<unknown>>["value"]
Expand Down
12 changes: 7 additions & 5 deletions src/util/Build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Option } from "fp-ts/Option";
import { Either, Left, Right } from "fp-ts/Either";
import type { AtPath } from "./AtPath";
import { IsRecord } from "./predicates";
import { AddDots, InitSegment, LastSegment } from "./segments";
import { AddNullSegments, InitSegment, LastSegment, UnescapeParenthesis } from "./segments";

type Operation = "augment" | "remove";

Expand All @@ -12,7 +12,7 @@ export type Build<
Original = unknown,
NewKey extends string = string,
Op extends Operation = "augment"
> = _Build<AddDots<Path>, Output, Original, NewKey, Op>;
> = _Build<AddNullSegments<Path>, Output, Original, NewKey, Op>;

type _Build<Path extends string, Output, Original, NewKey extends string, Op extends Operation> = Path extends ""
? Output
Expand Down Expand Up @@ -42,7 +42,7 @@ type FromScratch<Segment extends string, New> = OnSegment<
} & { readonly [K in Discriminant]: string }
: never;
tuple: New[] & { readonly [K in Segment]: New };
record: { readonly [K in Segment]: New };
record: { readonly [K in UnescapeParenthesis<Segment>]: New };
}
>;

Expand All @@ -62,7 +62,7 @@ type Augment<Segment extends string, Old, New, NewKey extends string, Op extends
| Exclude<Old, Record<Discriminant, Member>>
: never;
tuple: Segment extends `[${infer TupleKey}]` ? AugmentRecord<TupleKey, Old, New, NewKey, Op> : never;
record: true extends IsRecord<Old> ? AugmentRecord<Segment, Old, New, NewKey, Op> : never;
record: true extends IsRecord<Old> ? AugmentRecord<UnescapeParenthesis<Segment>, Old, New, NewKey, Op> : never;
}
>;

Expand Down Expand Up @@ -96,7 +96,9 @@ type OnSegment<
tuple: unknown;
record: unknown;
}
> = S extends "?"
> = S extends `(${string}`
? Handler["record"]
: S extends "?"
? Handler["null"]
: S extends "?some"
? Handler["option"]
Expand Down
13 changes: 11 additions & 2 deletions src/util/Paths.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Option, Some } from "fp-ts/Option";
import type { Either, Left, Right } from "fp-ts/Either";
import type { IsNull, IsRecord, IsNonTupleArray, TupleKeyof, IsNonStructRecord } from "./predicates";
import { LastSegment } from "./segments";
import { EscapeSpecialChars, LastSegment } from "./segments";
import type { Cases, Discriminant } from "./sum";

// Credit to Stefan Baumgartner
Expand Down Expand Up @@ -116,7 +116,7 @@ type UpsertableKeys<A> = Extract<

type Operation = "static" | "dynamic" | "upsert";

export type Paths<A, Op extends Operation = "static"> = _Paths<{ "": A }, Op>;
export type Paths<A, Op extends Operation = "static"> = _Paths<{ "": EscapeKeys<A> }, Op>;

type _Paths<A, Op extends Operation, Acc extends string = never> = true extends IsRecord<A>
? _Paths<
Expand All @@ -130,3 +130,12 @@ type _Paths<A, Op extends Operation, Acc extends string = never> = true extends
: ExtractChangeableKeys<keyof A>)
>
: Acc;

// Not tail recursive!!
type EscapeKeys<A> = A extends Record<string, any>
? A extends unknown[]
? A
: {
[K in keyof A as EscapeSpecialChars<Extract<K, string>>]: EscapeKeys<A[K]>;
}
: A;
63 changes: 49 additions & 14 deletions src/util/monocle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,53 @@ import * as Op from "monocle-ts/lib/Optional";
import * as Tr from "monocle-ts/lib/Traversal";

export const isPathLens = (path: string): boolean =>
!path.includes("?") &&
!path.includes(":") &&
!path.includes("?some") &&
!path.includes("?left") &&
!path.includes("?right") &&
!path.includes("[]>") &&
!path.includes("{}>");
!split(path).some(
(s) => ["?", "?some", "?left", "right", "[]>", "{}>"].includes(s) || (!s.startsWith("(") && s.includes(":"))
);

const splitIntoSegments = (path: string): string[] => {
const segments = path.split(".");
export const isPathTraversal = (path: string): boolean => split(path).some((s) => ["[]>", "{}>"].includes(s));

const lastSegment = (path: string): string => {
if (path === "") {
return path;
}
const escapeable = path.match(/\((.*)\*(.*)\)$/);
if (escapeable) {
return escapeable[2] as string;
}
const escapeable2 = path.match(/\((.*)\)$/);
if (escapeable2) {
return escapeable2[0] as string;
}
const finalSegment = path.match(/(.*)\.(.*)/);
if (finalSegment) {
return finalSegment[2] as string;
}
return path;
};

const initSegment = (path: string): string => {
if (path === "") {
return path;
}
const escapeable = path.match(/(.*)\.\((.*)\*(.*)\)$/);
if (escapeable) {
return escapeable[1] as string;
}
return path.substring(0, path.lastIndexOf(lastSegment(path)) - 1);
};

const splitIntoSegments = (path: string, acc: string[] = []): string[] => {
const init = initSegment(path);
const last = lastSegment(path);
if (init === "") {
return [last, ...acc];
}
return splitIntoSegments(init, [last, ...acc]);
};

const split = (path: string): string[] => {
const segments = splitIntoSegments(path, []);
return segments.flatMap((segment) => {
if (
segment.includes("?some") ||
Expand All @@ -35,10 +72,8 @@ const splitIntoSegments = (path: string): string[] => {
});
};

export const isPathTraversal = (path: string): boolean => path.includes("[]>") || path.includes("{}>");

export const optionalFromPath = (path: string): Op.Optional<any, any> => {
const opt = splitIntoSegments(path).reduce((acc, cur) => {
const opt = split(path).reduce((acc, cur) => {
if (cur === "?") {
return pipe(acc, Op.fromNullable);
} else if (cur === "?some") {
Expand All @@ -65,7 +100,7 @@ export const optionalFromPath = (path: string): Op.Optional<any, any> => {
};

export const traversalFromPath = (path: string): Tr.Traversal<any, any> => {
const opt = splitIntoSegments(path).reduce((acc, cur) => {
const opt = split(path).reduce((acc, cur) => {
if (cur === "?") {
return pipe(acc, Tr.fromNullable);
} else if (cur === "?some") {
Expand Down Expand Up @@ -98,7 +133,7 @@ export const traversalFromPath = (path: string): Tr.Traversal<any, any> => {
};

export const lensFromPath = (path: string): L.Lens<any, any> => {
const lens = splitIntoSegments(path).reduce((acc, cur) => {
const lens = split(path).reduce((acc, cur) => {
if (cur.includes("[") && cur.includes("]") && cur.indexOf("[") < cur.indexOf("]")) {
const component = cur.substring(cur.indexOf("[") + 1, cur.indexOf("]"));
return pipe(acc, L.component(Number.parseInt(component, 10)));
Expand Down
Loading

0 comments on commit 0dc0392

Please sign in to comment.