Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.lock

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

54 changes: 43 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,27 @@ span. `transform` requires explicit findings; `scan_and_transform` (or
convenience. Results include the transformed text and an ordered record for
every applied replacement, including its output byte and code-point ranges.

Object-oriented bindings use a discriminated configuration:
Transformation calls require an envelope with a default strategy. It can also
select entity types, override the strategy per entity, and exempt exact or
full-match regex values:

```text
{ strategy: "redact" }
{ strategy: "remove" }
{ strategy: "mask", character: "*", reveal: { direction: "last", count: 4 } }
```js
{
default: { strategy: "redact" },
entities: ["EMAIL", "PHONE"],
overrides: {
PHONE: { strategy: "mask", reveal: { direction: "last", count: 4 } },
},
allow: {
exact: { EMAIL: ["support@example.com"] },
regex: { EMAIL: [{ pattern: ".+@example\\.org" }] },
},
}
```

`scan_and_transform` uses `{ scan?: { locale?: string }, transform: ... }` so
detection settings remain separate from transformation policy.

## Packages

| Runtime | Distribution | Import | Status |
Expand All @@ -47,7 +60,10 @@ cargo add datafog-core
```

```rust
use datafog_core::{scan, scan_and_transform, TransformationStrategy};
use datafog_core::{
scan, scan_and_transform, ScanAndTransformConfig, TransformationConfig,
TransformationStrategy,
};

let findings = scan("Email jane@example.com");
assert_eq!(findings[0].entity_type, "EMAIL");
Expand All @@ -56,7 +72,9 @@ assert_eq!(findings[0].byte_range.start, 6);

let result = scan_and_transform(
"Email jane@example.com",
TransformationStrategy::Redact,
&ScanAndTransformConfig::new(TransformationConfig::new(
TransformationStrategy::Redact,
)),
).unwrap();
assert_eq!(result.text, "Email [EMAIL]");
```
Expand All @@ -75,12 +93,22 @@ print(findings[0].entity_type) # EMAIL
print(findings[0].matched_text) # jane@example.com
print(findings[0].byte_range.start) # 6

result = scan_and_transform("Email jane@example.com", {"strategy": "redact"})
result = scan_and_transform(
"Email jane@example.com",
{"transform": {"default": {"strategy": "redact"}}},
)
assert result.text == "Email [EMAIL]"

masked = scan_and_transform(
"Email jane@example.com",
{"strategy": "mask", "reveal": {"direction": "last", "count": 4}},
{
"transform": {
"default": {
"strategy": "mask",
"reveal": {"direction": "last", "count": 4},
}
}
},
)
assert masked.text == "Email ************.com"
```
Expand All @@ -94,7 +122,9 @@ import { scan, scanAndTransform } from "@datafog/node";

console.log(scan("Email jane@example.com"));
console.log(
scanAndTransform("Email jane@example.com", { strategy: "redact" }).text,
scanAndTransform("Email jane@example.com", {
transform: { default: { strategy: "redact" } },
}).text,
);
```

Expand All @@ -110,7 +140,9 @@ import { init, scan, scanAndTransform } from "@datafog/wasm";
await init();
console.log(scan("Email jane@example.com"));
console.log(
scanAndTransform("Email jane@example.com", { strategy: "redact" }).text,
scanAndTransform("Email jane@example.com", {
transform: { default: { strategy: "redact" } },
}).text,
);
```

Expand Down
3 changes: 2 additions & 1 deletion bindings/node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ crate-type = ["cdylib"]

[dependencies]
datafog-core = { path = "../../crates/core" }
napi = "=3.12.2"
napi = { version = "=3.12.2", features = ["serde-json"] }
napi-derive = "=3.6.3"
serde_json = "1"

[build-dependencies]
napi-build = "=2.4.1"
40 changes: 39 additions & 1 deletion bindings/node/dts-header.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,49 @@ export interface MaskRevealConfig {
readonly count: number;
}

export type TransformationConfig =
export type TransformationStrategyConfig =
| { readonly strategy: "redact" }
| { readonly strategy: "remove" }
| {
readonly strategy: "mask";
readonly character?: string;
readonly reveal?: MaskRevealConfig;
};

export interface RegexAllowRule {
readonly pattern: string;
readonly case_sensitive?: boolean;
}

export interface AllowConfig {
readonly exact?: Readonly<Record<EntityType, readonly string[]>>;
readonly regex?: Readonly<Record<EntityType, readonly RegexAllowRule[]>>;
}

export interface TransformationConfig {
readonly default: TransformationStrategyConfig;
readonly entities?: readonly EntityType[];
readonly overrides?: Readonly<Record<EntityType, TransformationStrategyConfig>>;
readonly allow?: AllowConfig;
}

export interface ScanConfig {
readonly locale?: string;
}

export interface ScanAndTransformConfig {
readonly scan?: ScanConfig;
readonly transform: TransformationConfig;
}

export type DataFogErrorCode =
| "invalid_configuration"
| "invalid_finding"
| "internal_error";

export declare class DataFogError extends Error {
readonly code: DataFogErrorCode;
readonly reason?: string;
readonly path?: string;
readonly findingIndex?: number;
}
55 changes: 41 additions & 14 deletions bindings/node/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,52 @@ export interface MaskRevealConfig {
readonly count: number;
}

export type TransformationConfig =
export type TransformationStrategyConfig =
| { readonly strategy: "redact" }
| { readonly strategy: "remove" }
| {
readonly strategy: "mask";
readonly character?: string;
readonly reveal?: MaskRevealConfig;
};

export interface RegexAllowRule {
readonly pattern: string;
readonly case_sensitive?: boolean;
}

export interface AllowConfig {
readonly exact?: Readonly<Record<EntityType, readonly string[]>>;
readonly regex?: Readonly<Record<EntityType, readonly RegexAllowRule[]>>;
}

export interface TransformationConfig {
readonly default: TransformationStrategyConfig;
readonly entities?: readonly EntityType[];
readonly overrides?: Readonly<Record<EntityType, TransformationStrategyConfig>>;
readonly allow?: AllowConfig;
}

export interface ScanConfig {
readonly locale?: string;
}

export interface ScanAndTransformConfig {
readonly scan?: ScanConfig;
readonly transform: TransformationConfig;
}

export type DataFogErrorCode =
| "invalid_configuration"
| "invalid_finding"
| "internal_error";

export declare class DataFogError extends Error {
readonly code: DataFogErrorCode;
readonly reason?: string;
readonly path?: string;
readonly findingIndex?: number;
}
export interface Finding {
readonly entityType: EntityType
readonly matchedText: string
Expand All @@ -26,22 +64,11 @@ export interface Finding {
readonly detectorVersion?: string
}

export interface NativeMaskRevealConfig {
direction: string
count: number
}

export interface NativeTransformationConfig {
strategy: TransformationStrategy
character?: string
reveal?: NativeMaskRevealConfig
}

/** Scan text for supported PII findings. */
export declare function scan(text: string): Array<Finding>
export declare function scan(text: string, config?: ScanConfig | undefined): Array<Finding>

/** Scan text and transform the detected findings. */
export declare function scanAndTransform(text: string, config: TransformationConfig): TransformResult
export declare function scanAndTransform(text: string, config: ScanAndTransformConfig): TransformResult

export interface TextRange {
readonly start: number
Expand Down
Loading
Loading