committed by
Sacha Al Himdani
parent
288c59a5f2
commit
f9216d30b2
@@ -1,7 +1,7 @@
|
|||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { Option } from "@probo/ui";
|
import { Option } from "@probo/ui";
|
||||||
import type {
|
import type {
|
||||||
ProcessingActivitySpecialOrCriminalData,
|
ProcessingActivitySpecialOrCriminalDatum,
|
||||||
ProcessingActivityLawfulBasis,
|
ProcessingActivityLawfulBasis,
|
||||||
ProcessingActivityDataProtectionImpactAssessment,
|
ProcessingActivityDataProtectionImpactAssessment,
|
||||||
ProcessingActivityTransferImpactAssessment,
|
ProcessingActivityTransferImpactAssessment,
|
||||||
@@ -11,7 +11,7 @@ export function SpecialOrCriminalDataOptions() {
|
|||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
const options: Array<{
|
const options: Array<{
|
||||||
value: ProcessingActivitySpecialOrCriminalData;
|
value: ProcessingActivitySpecialOrCriminalDatum;
|
||||||
label: string;
|
label: string;
|
||||||
}> = [
|
}> = [
|
||||||
{ value: "YES", label: __("Yes") },
|
{ value: "YES", label: __("Yes") },
|
||||||
@@ -56,16 +56,19 @@ export function LawfulBasisOptions() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLawfulBasisLabel(value: ProcessingActivityLawfulBasis | null | undefined, __: (key: string) => string): string {
|
export function getLawfulBasisLabel(
|
||||||
|
value: ProcessingActivityLawfulBasis | null | undefined,
|
||||||
|
__: (key: string) => string
|
||||||
|
): string {
|
||||||
if (!value) return "-";
|
if (!value) return "-";
|
||||||
|
|
||||||
const labels = {
|
const labels = {
|
||||||
"CONSENT": __("Consent"),
|
CONSENT: __("Consent"),
|
||||||
"CONTRACTUAL_NECESSITY": __("Contractual Necessity"),
|
CONTRACTUAL_NECESSITY: __("Contractual Necessity"),
|
||||||
"LEGAL_OBLIGATION": __("Legal Obligation"),
|
LEGAL_OBLIGATION: __("Legal Obligation"),
|
||||||
"LEGITIMATE_INTEREST": __("Legitimate Interest"),
|
LEGITIMATE_INTEREST: __("Legitimate Interest"),
|
||||||
"PUBLIC_TASK": __("Public Task"),
|
PUBLIC_TASK: __("Public Task"),
|
||||||
"VITAL_INTERESTS": __("Vital Interests"),
|
VITAL_INTERESTS: __("Vital Interests"),
|
||||||
};
|
};
|
||||||
|
|
||||||
return labels[value] || value;
|
return labels[value] || value;
|
||||||
@@ -79,12 +82,18 @@ export function TransferSafeguardsOptions() {
|
|||||||
label: string;
|
label: string;
|
||||||
}> = [
|
}> = [
|
||||||
{ value: "__NONE__", label: __("None") },
|
{ value: "__NONE__", label: __("None") },
|
||||||
{ value: "STANDARD_CONTRACTUAL_CLAUSES", label: __("Standard Contractual Clauses") },
|
{
|
||||||
|
value: "STANDARD_CONTRACTUAL_CLAUSES",
|
||||||
|
label: __("Standard Contractual Clauses"),
|
||||||
|
},
|
||||||
{ value: "BINDING_CORPORATE_RULES", label: __("Binding Corporate Rules") },
|
{ value: "BINDING_CORPORATE_RULES", label: __("Binding Corporate Rules") },
|
||||||
{ value: "ADEQUACY_DECISION", label: __("Adequacy Decision") },
|
{ value: "ADEQUACY_DECISION", label: __("Adequacy Decision") },
|
||||||
{ value: "DEROGATIONS", label: __("Derogations") },
|
{ value: "DEROGATIONS", label: __("Derogations") },
|
||||||
{ value: "CODES_OF_CONDUCT", label: __("Codes of Conduct") },
|
{ value: "CODES_OF_CONDUCT", label: __("Codes of Conduct") },
|
||||||
{ value: "CERTIFICATION_MECHANISMS", label: __("Certification Mechanisms") },
|
{
|
||||||
|
value: "CERTIFICATION_MECHANISMS",
|
||||||
|
label: __("Certification Mechanisms"),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -107,6 +107,14 @@ export function useVendorForm(vendorKey: useVendorFormFragment$key) {
|
|||||||
id: vendor.id,
|
id: vendor.id,
|
||||||
...data,
|
...data,
|
||||||
description: data.description || null,
|
description: data.description || null,
|
||||||
|
statusPageUrl: data.statusPageUrl || null,
|
||||||
|
termsOfServiceUrl: data.termsOfServiceUrl || null,
|
||||||
|
privacyPolicyUrl: data.privacyPolicyUrl || null,
|
||||||
|
serviceLevelAgreementUrl: data.serviceLevelAgreementUrl || null,
|
||||||
|
dataProcessingAgreementUrl: data.dataProcessingAgreementUrl || null,
|
||||||
|
websiteUrl: data.websiteUrl || null,
|
||||||
|
securityPageUrl: data.securityPageUrl || null,
|
||||||
|
trustPageUrl: data.trustPageUrl || null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useMutation } from "react-relay";
|
|||||||
import { useConfirm } from "@probo/ui";
|
import { useConfirm } from "@probo/ui";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { promisifyMutation, sprintf } from "@probo/helpers";
|
import { promisifyMutation, sprintf } from "@probo/helpers";
|
||||||
|
import { useMutationWithToasts } from "../useMutationWithToasts";
|
||||||
|
|
||||||
export const assetsQuery = graphql`
|
export const assetsQuery = graphql`
|
||||||
query AssetGraphListQuery($organizationId: ID!, $snapshotId: ID) {
|
query AssetGraphListQuery($organizationId: ID!, $snapshotId: ID) {
|
||||||
@@ -197,8 +198,11 @@ export const useCreateAsset = (connectionId: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useUpdateAsset = () => {
|
export const useUpdateAsset = () => {
|
||||||
const [mutate] = useMutation(updateAssetMutation);
|
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
const [mutate] = useMutationWithToasts(updateAssetMutation, {
|
||||||
|
successMessage: __("Asset updated successfully"),
|
||||||
|
errorMessage: __("Failed to update asset"),
|
||||||
|
});
|
||||||
|
|
||||||
return (input: {
|
return (input: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -213,7 +217,7 @@ export const useUpdateAsset = () => {
|
|||||||
return alert(__("Failed to update asset: asset ID is required"));
|
return alert(__("Failed to update asset: asset ID is required"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return promisifyMutation(mutate)({
|
return mutate({
|
||||||
variables: {
|
variables: {
|
||||||
input,
|
input,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export const useCreateAudit = (connectionId: string) => {
|
|||||||
return (input: {
|
return (input: {
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
frameworkId: string;
|
frameworkId: string;
|
||||||
name?: string;
|
name?: string | null;
|
||||||
validFrom?: string;
|
validFrom?: string;
|
||||||
validUntil?: string;
|
validUntil?: string;
|
||||||
reportKey?: string;
|
reportKey?: string;
|
||||||
@@ -187,7 +187,7 @@ export const useUpdateAudit = () => {
|
|||||||
|
|
||||||
return (input: {
|
return (input: {
|
||||||
id: string;
|
id: string;
|
||||||
name?: string;
|
name?: string | null;
|
||||||
validFrom?: string | null;
|
validFrom?: string | null;
|
||||||
validUntil?: string | null;
|
validUntil?: string | null;
|
||||||
state?: string;
|
state?: string;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<d01ca9f47a4ce6e732cffd5deb9150c6>>
|
* @generated SignedSource<<a804557b48f2dbc4749c8399e708b9fe>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -11,9 +11,9 @@
|
|||||||
import { ConcreteRequest } from 'relay-runtime';
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
export type ProcessingActivityDataProtectionImpactAssessment = "NEEDED" | "NOT_NEEDED";
|
export type ProcessingActivityDataProtectionImpactAssessment = "NEEDED" | "NOT_NEEDED";
|
||||||
export type ProcessingActivityLawfulBasis = "CONSENT" | "CONTRACTUAL_NECESSITY" | "LEGAL_OBLIGATION" | "LEGITIMATE_INTEREST" | "PUBLIC_TASK" | "VITAL_INTERESTS";
|
export type ProcessingActivityLawfulBasis = "CONSENT" | "CONTRACTUAL_NECESSITY" | "LEGAL_OBLIGATION" | "LEGITIMATE_INTEREST" | "PUBLIC_TASK" | "VITAL_INTERESTS";
|
||||||
export type ProcessingActivitySpecialOrCriminalData = "NO" | "POSSIBLE" | "YES";
|
export type ProcessingActivitySpecialOrCriminalDatum = "NO" | "POSSIBLE" | "YES";
|
||||||
export type ProcessingActivityTransferImpactAssessment = "NEEDED" | "NOT_NEEDED";
|
export type ProcessingActivityTransferImpactAssessment = "NEEDED" | "NOT_NEEDED";
|
||||||
export type ProcessingActivityTransferSafeguards = "ADEQUACY_DECISION" | "BINDING_CORPORATE_RULES" | "CERTIFICATION_MECHANISMS" | "CODES_OF_CONDUCT" | "DEROGATIONS" | "STANDARD_CONTRACTUAL_CLAUSES";
|
export type ProcessingActivityTransferSafeguard = "ADEQUACY_DECISION" | "BINDING_CORPORATE_RULES" | "CERTIFICATION_MECHANISMS" | "CODES_OF_CONDUCT" | "DEROGATIONS" | "STANDARD_CONTRACTUAL_CLAUSES";
|
||||||
export type CreateProcessingActivityInput = {
|
export type CreateProcessingActivityInput = {
|
||||||
consentEvidenceLink?: string | null | undefined;
|
consentEvidenceLink?: string | null | undefined;
|
||||||
dataProtectionImpactAssessment: ProcessingActivityDataProtectionImpactAssessment;
|
dataProtectionImpactAssessment: ProcessingActivityDataProtectionImpactAssessment;
|
||||||
@@ -28,9 +28,9 @@ export type CreateProcessingActivityInput = {
|
|||||||
recipients?: string | null | undefined;
|
recipients?: string | null | undefined;
|
||||||
retentionPeriod?: string | null | undefined;
|
retentionPeriod?: string | null | undefined;
|
||||||
securityMeasures?: string | null | undefined;
|
securityMeasures?: string | null | undefined;
|
||||||
specialOrCriminalData: ProcessingActivitySpecialOrCriminalData;
|
specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum;
|
||||||
transferImpactAssessment: ProcessingActivityTransferImpactAssessment;
|
transferImpactAssessment: ProcessingActivityTransferImpactAssessment;
|
||||||
transferSafeguards?: ProcessingActivityTransferSafeguards | null | undefined;
|
transferSafeguards?: ProcessingActivityTransferSafeguard | null | undefined;
|
||||||
vendorIds?: ReadonlyArray<string> | null | undefined;
|
vendorIds?: ReadonlyArray<string> | null | undefined;
|
||||||
};
|
};
|
||||||
export type ProcessingActivityGraphCreateMutation$variables = {
|
export type ProcessingActivityGraphCreateMutation$variables = {
|
||||||
@@ -55,9 +55,9 @@ export type ProcessingActivityGraphCreateMutation$data = {
|
|||||||
readonly recipients: string | null | undefined;
|
readonly recipients: string | null | undefined;
|
||||||
readonly retentionPeriod: string | null | undefined;
|
readonly retentionPeriod: string | null | undefined;
|
||||||
readonly securityMeasures: string | null | undefined;
|
readonly securityMeasures: string | null | undefined;
|
||||||
readonly specialOrCriminalData: ProcessingActivitySpecialOrCriminalData;
|
readonly specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum;
|
||||||
readonly transferImpactAssessment: ProcessingActivityTransferImpactAssessment;
|
readonly transferImpactAssessment: ProcessingActivityTransferImpactAssessment;
|
||||||
readonly transferSafeguards: ProcessingActivityTransferSafeguards | null | undefined;
|
readonly transferSafeguards: ProcessingActivityTransferSafeguard | null | undefined;
|
||||||
readonly vendors: {
|
readonly vendors: {
|
||||||
readonly edges: ReadonlyArray<{
|
readonly edges: ReadonlyArray<{
|
||||||
readonly node: {
|
readonly node: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<5dea7ccaf98a1223d9e4cc1ecdfbb0bc>>
|
* @generated SignedSource<<736925258373b1116c286fc16d19ad7d>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -11,9 +11,9 @@
|
|||||||
import { ConcreteRequest } from 'relay-runtime';
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
export type ProcessingActivityDataProtectionImpactAssessment = "NEEDED" | "NOT_NEEDED";
|
export type ProcessingActivityDataProtectionImpactAssessment = "NEEDED" | "NOT_NEEDED";
|
||||||
export type ProcessingActivityLawfulBasis = "CONSENT" | "CONTRACTUAL_NECESSITY" | "LEGAL_OBLIGATION" | "LEGITIMATE_INTEREST" | "PUBLIC_TASK" | "VITAL_INTERESTS";
|
export type ProcessingActivityLawfulBasis = "CONSENT" | "CONTRACTUAL_NECESSITY" | "LEGAL_OBLIGATION" | "LEGITIMATE_INTEREST" | "PUBLIC_TASK" | "VITAL_INTERESTS";
|
||||||
export type ProcessingActivitySpecialOrCriminalData = "NO" | "POSSIBLE" | "YES";
|
export type ProcessingActivitySpecialOrCriminalDatum = "NO" | "POSSIBLE" | "YES";
|
||||||
export type ProcessingActivityTransferImpactAssessment = "NEEDED" | "NOT_NEEDED";
|
export type ProcessingActivityTransferImpactAssessment = "NEEDED" | "NOT_NEEDED";
|
||||||
export type ProcessingActivityTransferSafeguards = "ADEQUACY_DECISION" | "BINDING_CORPORATE_RULES" | "CERTIFICATION_MECHANISMS" | "CODES_OF_CONDUCT" | "DEROGATIONS" | "STANDARD_CONTRACTUAL_CLAUSES";
|
export type ProcessingActivityTransferSafeguard = "ADEQUACY_DECISION" | "BINDING_CORPORATE_RULES" | "CERTIFICATION_MECHANISMS" | "CODES_OF_CONDUCT" | "DEROGATIONS" | "STANDARD_CONTRACTUAL_CLAUSES";
|
||||||
export type VendorCategory = "ANALYTICS" | "CLOUD_MONITORING" | "CLOUD_PROVIDER" | "COLLABORATION" | "CUSTOMER_SUPPORT" | "DATA_STORAGE_AND_PROCESSING" | "DOCUMENT_MANAGEMENT" | "EMPLOYEE_MANAGEMENT" | "ENGINEERING" | "FINANCE" | "IDENTITY_PROVIDER" | "IT" | "MARKETING" | "OFFICE_OPERATIONS" | "OTHER" | "PASSWORD_MANAGEMENT" | "PRODUCT_AND_DESIGN" | "PROFESSIONAL_SERVICES" | "RECRUITING" | "SALES" | "SECURITY" | "VERSION_CONTROL";
|
export type VendorCategory = "ANALYTICS" | "CLOUD_MONITORING" | "CLOUD_PROVIDER" | "COLLABORATION" | "CUSTOMER_SUPPORT" | "DATA_STORAGE_AND_PROCESSING" | "DOCUMENT_MANAGEMENT" | "EMPLOYEE_MANAGEMENT" | "ENGINEERING" | "FINANCE" | "IDENTITY_PROVIDER" | "IT" | "MARKETING" | "OFFICE_OPERATIONS" | "OTHER" | "PASSWORD_MANAGEMENT" | "PRODUCT_AND_DESIGN" | "PROFESSIONAL_SERVICES" | "RECRUITING" | "SALES" | "SECURITY" | "VERSION_CONTROL";
|
||||||
export type ProcessingActivityGraphNodeQuery$variables = {
|
export type ProcessingActivityGraphNodeQuery$variables = {
|
||||||
processingActivityId: string;
|
processingActivityId: string;
|
||||||
@@ -39,9 +39,9 @@ export type ProcessingActivityGraphNodeQuery$data = {
|
|||||||
readonly retentionPeriod?: string | null | undefined;
|
readonly retentionPeriod?: string | null | undefined;
|
||||||
readonly securityMeasures?: string | null | undefined;
|
readonly securityMeasures?: string | null | undefined;
|
||||||
readonly snapshotId?: string | null | undefined;
|
readonly snapshotId?: string | null | undefined;
|
||||||
readonly specialOrCriminalData?: ProcessingActivitySpecialOrCriminalData;
|
readonly specialOrCriminalData?: ProcessingActivitySpecialOrCriminalDatum;
|
||||||
readonly transferImpactAssessment?: ProcessingActivityTransferImpactAssessment;
|
readonly transferImpactAssessment?: ProcessingActivityTransferImpactAssessment;
|
||||||
readonly transferSafeguards?: ProcessingActivityTransferSafeguards | null | undefined;
|
readonly transferSafeguards?: ProcessingActivityTransferSafeguard | null | undefined;
|
||||||
readonly updatedAt?: any;
|
readonly updatedAt?: any;
|
||||||
readonly vendors?: {
|
readonly vendors?: {
|
||||||
readonly edges: ReadonlyArray<{
|
readonly edges: ReadonlyArray<{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<08099a83039838c6c79dbf1ca7c99034>>
|
* @generated SignedSource<<7004a9f42c1d7e16eadf8adbdb613b2a>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -11,9 +11,9 @@
|
|||||||
import { ConcreteRequest } from 'relay-runtime';
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
export type ProcessingActivityDataProtectionImpactAssessment = "NEEDED" | "NOT_NEEDED";
|
export type ProcessingActivityDataProtectionImpactAssessment = "NEEDED" | "NOT_NEEDED";
|
||||||
export type ProcessingActivityLawfulBasis = "CONSENT" | "CONTRACTUAL_NECESSITY" | "LEGAL_OBLIGATION" | "LEGITIMATE_INTEREST" | "PUBLIC_TASK" | "VITAL_INTERESTS";
|
export type ProcessingActivityLawfulBasis = "CONSENT" | "CONTRACTUAL_NECESSITY" | "LEGAL_OBLIGATION" | "LEGITIMATE_INTEREST" | "PUBLIC_TASK" | "VITAL_INTERESTS";
|
||||||
export type ProcessingActivitySpecialOrCriminalData = "NO" | "POSSIBLE" | "YES";
|
export type ProcessingActivitySpecialOrCriminalDatum = "NO" | "POSSIBLE" | "YES";
|
||||||
export type ProcessingActivityTransferImpactAssessment = "NEEDED" | "NOT_NEEDED";
|
export type ProcessingActivityTransferImpactAssessment = "NEEDED" | "NOT_NEEDED";
|
||||||
export type ProcessingActivityTransferSafeguards = "ADEQUACY_DECISION" | "BINDING_CORPORATE_RULES" | "CERTIFICATION_MECHANISMS" | "CODES_OF_CONDUCT" | "DEROGATIONS" | "STANDARD_CONTRACTUAL_CLAUSES";
|
export type ProcessingActivityTransferSafeguard = "ADEQUACY_DECISION" | "BINDING_CORPORATE_RULES" | "CERTIFICATION_MECHANISMS" | "CODES_OF_CONDUCT" | "DEROGATIONS" | "STANDARD_CONTRACTUAL_CLAUSES";
|
||||||
export type UpdateProcessingActivityInput = {
|
export type UpdateProcessingActivityInput = {
|
||||||
consentEvidenceLink?: string | null | undefined;
|
consentEvidenceLink?: string | null | undefined;
|
||||||
dataProtectionImpactAssessment?: ProcessingActivityDataProtectionImpactAssessment | null | undefined;
|
dataProtectionImpactAssessment?: ProcessingActivityDataProtectionImpactAssessment | null | undefined;
|
||||||
@@ -28,9 +28,9 @@ export type UpdateProcessingActivityInput = {
|
|||||||
recipients?: string | null | undefined;
|
recipients?: string | null | undefined;
|
||||||
retentionPeriod?: string | null | undefined;
|
retentionPeriod?: string | null | undefined;
|
||||||
securityMeasures?: string | null | undefined;
|
securityMeasures?: string | null | undefined;
|
||||||
specialOrCriminalData?: ProcessingActivitySpecialOrCriminalData | null | undefined;
|
specialOrCriminalData?: ProcessingActivitySpecialOrCriminalDatum | null | undefined;
|
||||||
transferImpactAssessment?: ProcessingActivityTransferImpactAssessment | null | undefined;
|
transferImpactAssessment?: ProcessingActivityTransferImpactAssessment | null | undefined;
|
||||||
transferSafeguards?: ProcessingActivityTransferSafeguards | null | undefined;
|
transferSafeguards?: ProcessingActivityTransferSafeguard | null | undefined;
|
||||||
vendorIds?: ReadonlyArray<string> | null | undefined;
|
vendorIds?: ReadonlyArray<string> | null | undefined;
|
||||||
};
|
};
|
||||||
export type ProcessingActivityGraphUpdateMutation$variables = {
|
export type ProcessingActivityGraphUpdateMutation$variables = {
|
||||||
@@ -52,9 +52,9 @@ export type ProcessingActivityGraphUpdateMutation$data = {
|
|||||||
readonly recipients: string | null | undefined;
|
readonly recipients: string | null | undefined;
|
||||||
readonly retentionPeriod: string | null | undefined;
|
readonly retentionPeriod: string | null | undefined;
|
||||||
readonly securityMeasures: string | null | undefined;
|
readonly securityMeasures: string | null | undefined;
|
||||||
readonly specialOrCriminalData: ProcessingActivitySpecialOrCriminalData;
|
readonly specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum;
|
||||||
readonly transferImpactAssessment: ProcessingActivityTransferImpactAssessment;
|
readonly transferImpactAssessment: ProcessingActivityTransferImpactAssessment;
|
||||||
readonly transferSafeguards: ProcessingActivityTransferSafeguards | null | undefined;
|
readonly transferSafeguards: ProcessingActivityTransferSafeguard | null | undefined;
|
||||||
readonly updatedAt: any;
|
readonly updatedAt: any;
|
||||||
readonly vendors: {
|
readonly vendors: {
|
||||||
readonly edges: ReadonlyArray<{
|
readonly edges: ReadonlyArray<{
|
||||||
|
|||||||
@@ -80,15 +80,11 @@ export default function AssetDetailsPage(props: Props) {
|
|||||||
const updateAsset = useUpdateAsset();
|
const updateAsset = useUpdateAsset();
|
||||||
|
|
||||||
const onSubmit = handleSubmit(async (formData) => {
|
const onSubmit = handleSubmit(async (formData) => {
|
||||||
try {
|
await updateAsset({
|
||||||
await updateAsset({
|
id: assetEntry?.id,
|
||||||
id: assetEntry?.id,
|
...formData,
|
||||||
...formData,
|
});
|
||||||
});
|
reset(formData);
|
||||||
reset(formData);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to update asset:", error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const breadcrumbAssetsUrl = isSnapshotMode && snapshotId
|
const breadcrumbAssetsUrl = isSnapshotMode && snapshotId
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import { getAuditStateLabel, getAuditStateVariant, auditStates, fileSize, sprint
|
|||||||
import type { AuditGraphNodeQuery } from "/hooks/graph/__generated__/AuditGraphNodeQuery.graphql";
|
import type { AuditGraphNodeQuery } from "/hooks/graph/__generated__/AuditGraphNodeQuery.graphql";
|
||||||
|
|
||||||
const updateAuditSchema = z.object({
|
const updateAuditSchema = z.object({
|
||||||
name: z.string().optional(),
|
name: z.string().nullable().optional(),
|
||||||
validFrom: z.string().optional(),
|
validFrom: z.string().optional(),
|
||||||
validUntil: z.string().optional(),
|
validUntil: z.string().optional(),
|
||||||
state: z.enum(["NOT_STARTED", "IN_PROGRESS", "COMPLETED", "REJECTED", "OUTDATED"]),
|
state: z.enum(["NOT_STARTED", "IN_PROGRESS", "COMPLETED", "REJECTED", "OUTDATED"]),
|
||||||
@@ -63,7 +63,7 @@ export default function AuditDetailsPage(props: Props) {
|
|||||||
|
|
||||||
const { control, formState, handleSubmit, register, reset } = useFormWithSchema(updateAuditSchema, {
|
const { control, formState, handleSubmit, register, reset } = useFormWithSchema(updateAuditSchema, {
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: auditEntry.name || "",
|
name: auditEntry.name || null,
|
||||||
validFrom: auditEntry.validFrom?.split('T')[0] || "",
|
validFrom: auditEntry.validFrom?.split('T')[0] || "",
|
||||||
validUntil: auditEntry.validUntil?.split('T')[0] || "",
|
validUntil: auditEntry.validUntil?.split('T')[0] || "",
|
||||||
state: auditEntry.state || "NOT_STARTED",
|
state: auditEntry.state || "NOT_STARTED",
|
||||||
@@ -82,7 +82,7 @@ export default function AuditDetailsPage(props: Props) {
|
|||||||
try {
|
try {
|
||||||
await updateAudit({
|
await updateAudit({
|
||||||
id: auditEntry.id,
|
id: auditEntry.id,
|
||||||
name: formData.name,
|
name: formData.name || null,
|
||||||
validFrom: formatDatetime(formData.validFrom) ?? null,
|
validFrom: formatDatetime(formData.validFrom) ?? null,
|
||||||
validUntil: formatDatetime(formData.validUntil) ?? null,
|
validUntil: formatDatetime(formData.validUntil) ?? null,
|
||||||
state: formData.state,
|
state: formData.state,
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export function CreateAuditDialog({
|
|||||||
await createAudit({
|
await createAudit({
|
||||||
organizationId,
|
organizationId,
|
||||||
frameworkId: data.frameworkId,
|
frameworkId: data.frameworkId,
|
||||||
name: data.name,
|
name: data.name || null,
|
||||||
validFrom: formatDatetime(data.validFrom),
|
validFrom: formatDatetime(data.validFrom),
|
||||||
validUntil: formatDatetime(data.validUntil),
|
validUntil: formatDatetime(data.validUntil),
|
||||||
state: data.state,
|
state: data.state,
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ const (
|
|||||||
AssetTypeVirtual AssetType = "VIRTUAL"
|
AssetTypeVirtual AssetType = "VIRTUAL"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func AssetTypes() []AssetType {
|
||||||
|
return []AssetType{
|
||||||
|
AssetTypePhysical,
|
||||||
|
AssetTypeVirtual,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (at AssetType) MarshalText() ([]byte, error) {
|
func (at AssetType) MarshalText() ([]byte, error) {
|
||||||
return []byte(at.String()), nil
|
return []byte(at.String()), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,16 @@ const (
|
|||||||
AuditStateOutdated AuditState = "OUTDATED"
|
AuditStateOutdated AuditState = "OUTDATED"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func AuditStates() []AuditState {
|
||||||
|
return []AuditState{
|
||||||
|
AuditStateNotStarted,
|
||||||
|
AuditStateInProgress,
|
||||||
|
AuditStateCompleted,
|
||||||
|
AuditStateRejected,
|
||||||
|
AuditStateOutdated,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (as AuditState) String() string {
|
func (as AuditState) String() string {
|
||||||
return string(as)
|
return string(as)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,15 @@ const (
|
|||||||
BusinessImpactCritical BusinessImpact = "CRITICAL"
|
BusinessImpactCritical BusinessImpact = "CRITICAL"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func BusinessImpacts() []BusinessImpact {
|
||||||
|
return []BusinessImpact{
|
||||||
|
BusinessImpactLow,
|
||||||
|
BusinessImpactMedium,
|
||||||
|
BusinessImpactHigh,
|
||||||
|
BusinessImpactCritical,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (i BusinessImpact) String() string {
|
func (i BusinessImpact) String() string {
|
||||||
return string(i)
|
return string(i)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ const (
|
|||||||
ConnectorProtocolOAuth2 ConnectorProtocol = "OAUTH2"
|
ConnectorProtocolOAuth2 ConnectorProtocol = "OAUTH2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ConnectorProtocols() []ConnectorProtocol {
|
||||||
|
return []ConnectorProtocol{
|
||||||
|
ConnectorProtocolOAuth2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cp ConnectorProtocol) String() string {
|
func (cp ConnectorProtocol) String() string {
|
||||||
return string(cp)
|
return string(cp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ const (
|
|||||||
ConnectorProviderSlack ConnectorProvider = "SLACK"
|
ConnectorProviderSlack ConnectorProvider = "SLACK"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ConnectorProviders() []ConnectorProvider {
|
||||||
|
return []ConnectorProvider{
|
||||||
|
ConnectorProviderSlack,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cp ConnectorProvider) String() string {
|
func (cp ConnectorProvider) String() string {
|
||||||
return string(cp)
|
return string(cp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ const (
|
|||||||
ContinualImprovementPriorityHigh ContinualImprovementPriority = "HIGH"
|
ContinualImprovementPriorityHigh ContinualImprovementPriority = "HIGH"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ContinualImprovementPriorities() []ContinualImprovementPriority {
|
||||||
|
return []ContinualImprovementPriority{
|
||||||
|
ContinualImprovementPriorityLow,
|
||||||
|
ContinualImprovementPriorityMedium,
|
||||||
|
ContinualImprovementPriorityHigh,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cip ContinualImprovementPriority) String() string {
|
func (cip ContinualImprovementPriority) String() string {
|
||||||
return string(cip)
|
return string(cip)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ const (
|
|||||||
ContinualImprovementStatusClosed ContinualImprovementStatus = "CLOSED"
|
ContinualImprovementStatusClosed ContinualImprovementStatus = "CLOSED"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ContinualImprovementStatuses() []ContinualImprovementStatus {
|
||||||
|
return []ContinualImprovementStatus{
|
||||||
|
ContinualImprovementStatusOpen,
|
||||||
|
ContinualImprovementStatusInProgress,
|
||||||
|
ContinualImprovementStatusClosed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cis ContinualImprovementStatus) String() string {
|
func (cis ContinualImprovementStatus) String() string {
|
||||||
return string(cis)
|
return string(cis)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ const (
|
|||||||
ControlStatusExcluded ControlStatus = "EXCLUDED"
|
ControlStatusExcluded ControlStatus = "EXCLUDED"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ControlStatuses() []ControlStatus {
|
||||||
|
return []ControlStatus{
|
||||||
|
ControlStatusIncluded,
|
||||||
|
ControlStatusExcluded,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cs ControlStatus) String() string {
|
func (cs ControlStatus) String() string {
|
||||||
return string(cs)
|
return string(cs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,3 +22,12 @@ const (
|
|||||||
DataClassificationConfidential DataClassification = "CONFIDENTIAL"
|
DataClassificationConfidential DataClassification = "CONFIDENTIAL"
|
||||||
DataClassificationSecret DataClassification = "SECRET"
|
DataClassificationSecret DataClassification = "SECRET"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func DataClassifications() []DataClassification {
|
||||||
|
return []DataClassification{
|
||||||
|
DataClassificationPublic,
|
||||||
|
DataClassificationInternal,
|
||||||
|
DataClassificationConfidential,
|
||||||
|
DataClassificationSecret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,6 +30,16 @@ const (
|
|||||||
DataSensitivityCritical DataSensitivity = "CRITICAL"
|
DataSensitivityCritical DataSensitivity = "CRITICAL"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func DataSensitivities() []DataSensitivity {
|
||||||
|
return []DataSensitivity{
|
||||||
|
DataSensitivityNone,
|
||||||
|
DataSensitivityLow,
|
||||||
|
DataSensitivityMedium,
|
||||||
|
DataSensitivityHigh,
|
||||||
|
DataSensitivityCritical,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (i DataSensitivity) String() string {
|
func (i DataSensitivity) String() string {
|
||||||
return string(i)
|
return string(i)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,15 @@ const (
|
|||||||
DocumentClassificationSecret DocumentClassification = "SECRET"
|
DocumentClassificationSecret DocumentClassification = "SECRET"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func DocumentClassifications() []DocumentClassification {
|
||||||
|
return []DocumentClassification{
|
||||||
|
DocumentClassificationPublic,
|
||||||
|
DocumentClassificationInternal,
|
||||||
|
DocumentClassificationConfidential,
|
||||||
|
DocumentClassificationSecret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (dc DocumentClassification) String() string {
|
func (dc DocumentClassification) String() string {
|
||||||
switch dc {
|
switch dc {
|
||||||
case DocumentClassificationPublic:
|
case DocumentClassificationPublic:
|
||||||
|
|||||||
@@ -30,6 +30,15 @@ const (
|
|||||||
DocumentTypeProcedure DocumentType = "PROCEDURE"
|
DocumentTypeProcedure DocumentType = "PROCEDURE"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func DocumentTypes() []DocumentType {
|
||||||
|
return []DocumentType{
|
||||||
|
DocumentTypeOther,
|
||||||
|
DocumentTypeISMS,
|
||||||
|
DocumentTypePolicy,
|
||||||
|
DocumentTypeProcedure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (dt DocumentType) MarshalText() ([]byte, error) {
|
func (dt DocumentType) MarshalText() ([]byte, error) {
|
||||||
return []byte(dt.String()), nil
|
return []byte(dt.String()), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,49 +21,49 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
OrganizationEntityType uint16 = iota
|
OrganizationEntityType uint16 = 0
|
||||||
FrameworkEntityType
|
FrameworkEntityType uint16 = 1
|
||||||
MeasureEntityType
|
MeasureEntityType uint16 = 2
|
||||||
TaskEntityType
|
TaskEntityType uint16 = 3
|
||||||
EvidenceEntityType
|
EvidenceEntityType uint16 = 4
|
||||||
ConnectorEntityType
|
ConnectorEntityType uint16 = 5
|
||||||
VendorRiskAssessmentEntityType
|
VendorRiskAssessmentEntityType uint16 = 6
|
||||||
VendorEntityType
|
VendorEntityType uint16 = 7
|
||||||
PeopleEntityType
|
PeopleEntityType uint16 = 8
|
||||||
VendorComplianceReportEntityType
|
VendorComplianceReportEntityType uint16 = 9
|
||||||
DocumentEntityType
|
DocumentEntityType uint16 = 10
|
||||||
UserEntityType
|
UserEntityType uint16 = 11
|
||||||
SessionEntityType
|
SessionEntityType uint16 = 12
|
||||||
EmailEntityType
|
EmailEntityType uint16 = 13
|
||||||
ControlEntityType
|
ControlEntityType uint16 = 14
|
||||||
RiskEntityType
|
RiskEntityType uint16 = 15
|
||||||
DocumentVersionEntityType
|
DocumentVersionEntityType uint16 = 16
|
||||||
DocumentVersionSignatureEntityType
|
DocumentVersionSignatureEntityType uint16 = 17
|
||||||
AssetEntityType
|
AssetEntityType uint16 = 18
|
||||||
DatumEntityType
|
DatumEntityType uint16 = 19
|
||||||
AuditEntityType
|
AuditEntityType uint16 = 20
|
||||||
ReportEntityType
|
ReportEntityType uint16 = 21
|
||||||
TrustCenterEntityType
|
TrustCenterEntityType uint16 = 22
|
||||||
TrustCenterAccessEntityType
|
TrustCenterAccessEntityType uint16 = 23
|
||||||
VendorBusinessAssociateAgreementEntityType
|
VendorBusinessAssociateAgreementEntityType uint16 = 24
|
||||||
FileEntityType
|
FileEntityType uint16 = 25
|
||||||
VendorContactEntityType
|
VendorContactEntityType uint16 = 26
|
||||||
VendorDataPrivacyAgreementEntityType
|
VendorDataPrivacyAgreementEntityType uint16 = 27
|
||||||
NonconformityEntityType
|
NonconformityEntityType uint16 = 28
|
||||||
ObligationEntityType
|
ObligationEntityType uint16 = 29
|
||||||
VendorServiceEntityType
|
VendorServiceEntityType uint16 = 30
|
||||||
SnapshotEntityType
|
SnapshotEntityType uint16 = 31
|
||||||
ContinualImprovementEntityType
|
ContinualImprovementEntityType uint16 = 32
|
||||||
ProcessingActivityEntityType
|
ProcessingActivityEntityType uint16 = 33
|
||||||
ExportJobEntityType
|
ExportJobEntityType uint16 = 34
|
||||||
TrustCenterReferenceEntityType
|
TrustCenterReferenceEntityType uint16 = 35
|
||||||
TrustCenterDocumentAccessEntityType
|
TrustCenterDocumentAccessEntityType uint16 = 36
|
||||||
CustomDomainEntityType
|
CustomDomainEntityType uint16 = 37
|
||||||
InvitationEntityType
|
InvitationEntityType uint16 = 38
|
||||||
MembershipEntityType
|
MembershipEntityType uint16 = 39
|
||||||
SlackMessageEntityType
|
SlackMessageEntityType uint16 = 40
|
||||||
TrustCenterFileEntityType
|
TrustCenterFileEntityType uint16 = 41
|
||||||
SAMLConfigurationEntityType
|
SAMLConfigurationEntityType uint16 = 42
|
||||||
UserAPIKeyEntityType
|
UserAPIKeyEntityType uint16 = 43
|
||||||
UserAPIKeyMembershipEntityType
|
UserAPIKeyMembershipEntityType uint16 = 44
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -30,6 +30,15 @@ const (
|
|||||||
MeasureStateImplemented
|
MeasureStateImplemented
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func MeasureStates() []MeasureState {
|
||||||
|
return []MeasureState{
|
||||||
|
MeasureStateNotStarted,
|
||||||
|
MeasureStateInProgress,
|
||||||
|
MeasureStateNotApplicable,
|
||||||
|
MeasureStateImplemented,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ms MeasureState) MarshalText() ([]byte, error) {
|
func (ms MeasureState) MarshalText() ([]byte, error) {
|
||||||
return []byte(ms.String()), nil
|
return []byte(ms.String()), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ const (
|
|||||||
NonconformityStatusClosed NonconformityStatus = "CLOSED"
|
NonconformityStatusClosed NonconformityStatus = "CLOSED"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func NonconformityStatuses() []NonconformityStatus {
|
||||||
|
return []NonconformityStatus{
|
||||||
|
NonconformityStatusOpen,
|
||||||
|
NonconformityStatusInProgress,
|
||||||
|
NonconformityStatusClosed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ncs NonconformityStatus) String() string {
|
func (ncs NonconformityStatus) String() string {
|
||||||
return string(ncs)
|
return string(ncs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ const (
|
|||||||
ObligationStatusCompliant ObligationStatus = "COMPLIANT"
|
ObligationStatusCompliant ObligationStatus = "COMPLIANT"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ObligationStatuses() []ObligationStatus {
|
||||||
|
return []ObligationStatus{
|
||||||
|
ObligationStatusNonCompliant,
|
||||||
|
ObligationStatusPartiallyCompliant,
|
||||||
|
ObligationStatusCompliant,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (os ObligationStatus) String() string {
|
func (os ObligationStatus) String() string {
|
||||||
return string(os)
|
return string(os)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ const (
|
|||||||
PeopleKindServiceAccount
|
PeopleKindServiceAccount
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func PeopleKinds() []PeopleKind {
|
||||||
|
return []PeopleKind{
|
||||||
|
PeopleKindEmployee,
|
||||||
|
PeopleKindContractor,
|
||||||
|
PeopleKindServiceAccount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ps PeopleKind) MarshalText() ([]byte, error) {
|
func (ps PeopleKind) MarshalText() ([]byte, error) {
|
||||||
return []byte(ps.String()), nil
|
return []byte(ps.String()), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ import (
|
|||||||
"maps"
|
"maps"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.probo.inc/probo/pkg/gid"
|
|
||||||
"go.probo.inc/probo/pkg/page"
|
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
"go.gearno.de/kit/pg"
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -36,13 +36,13 @@ type (
|
|||||||
Purpose *string `db:"purpose"`
|
Purpose *string `db:"purpose"`
|
||||||
DataSubjectCategory *string `db:"data_subject_category"`
|
DataSubjectCategory *string `db:"data_subject_category"`
|
||||||
PersonalDataCategory *string `db:"personal_data_category"`
|
PersonalDataCategory *string `db:"personal_data_category"`
|
||||||
SpecialOrCriminalData ProcessingActivitySpecialOrCriminalData `db:"special_or_criminal_data"`
|
SpecialOrCriminalData ProcessingActivitySpecialOrCriminalDatum `db:"special_or_criminal_data"`
|
||||||
ConsentEvidenceLink *string `db:"consent_evidence_link"`
|
ConsentEvidenceLink *string `db:"consent_evidence_link"`
|
||||||
LawfulBasis ProcessingActivityLawfulBasis `db:"lawful_basis"`
|
LawfulBasis ProcessingActivityLawfulBasis `db:"lawful_basis"`
|
||||||
Recipients *string `db:"recipients"`
|
Recipients *string `db:"recipients"`
|
||||||
Location *string `db:"location"`
|
Location *string `db:"location"`
|
||||||
InternationalTransfers bool `db:"international_transfers"`
|
InternationalTransfers bool `db:"international_transfers"`
|
||||||
TransferSafeguards *ProcessingActivityTransferSafeguards `db:"transfer_safeguards"`
|
TransferSafeguard *ProcessingActivityTransferSafeguard `db:"transfer_safeguards"`
|
||||||
RetentionPeriod *string `db:"retention_period"`
|
RetentionPeriod *string `db:"retention_period"`
|
||||||
SecurityMeasures *string `db:"security_measures"`
|
SecurityMeasures *string `db:"security_measures"`
|
||||||
DataProtectionImpactAssessment ProcessingActivityDataProtectionImpactAssessment `db:"data_protection_impact_assessment"`
|
DataProtectionImpactAssessment ProcessingActivityDataProtectionImpactAssessment `db:"data_protection_impact_assessment"`
|
||||||
@@ -290,7 +290,7 @@ INSERT INTO processing_activities (
|
|||||||
"recipients": p.Recipients,
|
"recipients": p.Recipients,
|
||||||
"location": p.Location,
|
"location": p.Location,
|
||||||
"international_transfers": p.InternationalTransfers,
|
"international_transfers": p.InternationalTransfers,
|
||||||
"transfer_safeguards": p.TransferSafeguards,
|
"transfer_safeguards": p.TransferSafeguard,
|
||||||
"retention_period": p.RetentionPeriod,
|
"retention_period": p.RetentionPeriod,
|
||||||
"security_measures": p.SecurityMeasures,
|
"security_measures": p.SecurityMeasures,
|
||||||
"data_protection_impact_assessment": p.DataProtectionImpactAssessment,
|
"data_protection_impact_assessment": p.DataProtectionImpactAssessment,
|
||||||
@@ -351,7 +351,7 @@ WHERE
|
|||||||
"recipients": p.Recipients,
|
"recipients": p.Recipients,
|
||||||
"location": p.Location,
|
"location": p.Location,
|
||||||
"international_transfers": p.InternationalTransfers,
|
"international_transfers": p.InternationalTransfers,
|
||||||
"transfer_safeguards": p.TransferSafeguards,
|
"transfer_safeguards": p.TransferSafeguard,
|
||||||
"retention_period": p.RetentionPeriod,
|
"retention_period": p.RetentionPeriod,
|
||||||
"security_measures": p.SecurityMeasures,
|
"security_measures": p.SecurityMeasures,
|
||||||
"data_protection_impact_assessment": p.DataProtectionImpactAssessment,
|
"data_protection_impact_assessment": p.DataProtectionImpactAssessment,
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ const (
|
|||||||
ProcessingActivityDataProtectionImpactAssessmentNotNeeded ProcessingActivityDataProtectionImpactAssessment = "NOT_NEEDED"
|
ProcessingActivityDataProtectionImpactAssessmentNotNeeded ProcessingActivityDataProtectionImpactAssessment = "NOT_NEEDED"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ProcessingActivityDataProtectionImpactAssessments() []ProcessingActivityDataProtectionImpactAssessment {
|
||||||
|
return []ProcessingActivityDataProtectionImpactAssessment{
|
||||||
|
ProcessingActivityDataProtectionImpactAssessmentNeeded,
|
||||||
|
ProcessingActivityDataProtectionImpactAssessmentNotNeeded,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p ProcessingActivityDataProtectionImpactAssessment) String() string {
|
func (p ProcessingActivityDataProtectionImpactAssessment) String() string {
|
||||||
return string(p)
|
return string(p)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,17 @@ const (
|
|||||||
ProcessingActivityLawfulBasisPublicTask ProcessingActivityLawfulBasis = "PUBLIC_TASK"
|
ProcessingActivityLawfulBasisPublicTask ProcessingActivityLawfulBasis = "PUBLIC_TASK"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ProcessingActivityLawfulBases() []ProcessingActivityLawfulBasis {
|
||||||
|
return []ProcessingActivityLawfulBasis{
|
||||||
|
ProcessingActivityLawfulBasisLegitimateInterest,
|
||||||
|
ProcessingActivityLawfulBasisConsent,
|
||||||
|
ProcessingActivityLawfulBasisContractualNecessity,
|
||||||
|
ProcessingActivityLawfulBasisLegalObligation,
|
||||||
|
ProcessingActivityLawfulBasisVitalInterests,
|
||||||
|
ProcessingActivityLawfulBasisPublicTask,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p ProcessingActivityLawfulBasis) String() string {
|
func (p ProcessingActivityLawfulBasis) String() string {
|
||||||
return string(p)
|
return string(p)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,19 +19,27 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProcessingActivitySpecialOrCriminalData string
|
type ProcessingActivitySpecialOrCriminalDatum string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ProcessingActivitySpecialOrCriminalDataYes ProcessingActivitySpecialOrCriminalData = "YES"
|
ProcessingActivitySpecialOrCriminalDatumYes ProcessingActivitySpecialOrCriminalDatum = "YES"
|
||||||
ProcessingActivitySpecialOrCriminalDataNo ProcessingActivitySpecialOrCriminalData = "NO"
|
ProcessingActivitySpecialOrCriminalDatumNo ProcessingActivitySpecialOrCriminalDatum = "NO"
|
||||||
ProcessingActivitySpecialOrCriminalDataPossible ProcessingActivitySpecialOrCriminalData = "POSSIBLE"
|
ProcessingActivitySpecialOrCriminalDatumPossible ProcessingActivitySpecialOrCriminalDatum = "POSSIBLE"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p ProcessingActivitySpecialOrCriminalData) String() string {
|
func ProcessingActivitySpecialOrCriminalData() []ProcessingActivitySpecialOrCriminalDatum {
|
||||||
|
return []ProcessingActivitySpecialOrCriminalDatum{
|
||||||
|
ProcessingActivitySpecialOrCriminalDatumYes,
|
||||||
|
ProcessingActivitySpecialOrCriminalDatumNo,
|
||||||
|
ProcessingActivitySpecialOrCriminalDatumPossible,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProcessingActivitySpecialOrCriminalDatum) String() string {
|
||||||
return string(p)
|
return string(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProcessingActivitySpecialOrCriminalData) Scan(value any) error {
|
func (p *ProcessingActivitySpecialOrCriminalDatum) Scan(value any) error {
|
||||||
var s string
|
var s string
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
@@ -39,22 +47,22 @@ func (p *ProcessingActivitySpecialOrCriminalData) Scan(value any) error {
|
|||||||
case []byte:
|
case []byte:
|
||||||
s = string(v)
|
s = string(v)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported type for ProcessingActivitySpecialOrCriminalData: %T", value)
|
return fmt.Errorf("unsupported type for ProcessingActivitySpecialOrCriminalDatum: %T", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch s {
|
switch s {
|
||||||
case "YES":
|
case "YES":
|
||||||
*p = ProcessingActivitySpecialOrCriminalDataYes
|
*p = ProcessingActivitySpecialOrCriminalDatumYes
|
||||||
case "NO":
|
case "NO":
|
||||||
*p = ProcessingActivitySpecialOrCriminalDataNo
|
*p = ProcessingActivitySpecialOrCriminalDatumNo
|
||||||
case "POSSIBLE":
|
case "POSSIBLE":
|
||||||
*p = ProcessingActivitySpecialOrCriminalDataPossible
|
*p = ProcessingActivitySpecialOrCriminalDatumPossible
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid ProcessingActivitySpecialOrCriminalData value: %q", s)
|
return fmt.Errorf("invalid ProcessingActivitySpecialOrCriminalDatum value: %q", s)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ProcessingActivitySpecialOrCriminalData) Value() (driver.Value, error) {
|
func (p ProcessingActivitySpecialOrCriminalDatum) Value() (driver.Value, error) {
|
||||||
return p.String(), nil
|
return p.String(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ const (
|
|||||||
ProcessingActivityTransferImpactAssessmentNotNeeded ProcessingActivityTransferImpactAssessment = "NOT_NEEDED"
|
ProcessingActivityTransferImpactAssessmentNotNeeded ProcessingActivityTransferImpactAssessment = "NOT_NEEDED"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ProcessingActivityTransferImpactAssessments() []ProcessingActivityTransferImpactAssessment {
|
||||||
|
return []ProcessingActivityTransferImpactAssessment{
|
||||||
|
ProcessingActivityTransferImpactAssessmentNeeded,
|
||||||
|
ProcessingActivityTransferImpactAssessmentNotNeeded,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p ProcessingActivityTransferImpactAssessment) String() string {
|
func (p ProcessingActivityTransferImpactAssessment) String() string {
|
||||||
return string(p)
|
return string(p)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,22 +19,33 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProcessingActivityTransferSafeguards string
|
type ProcessingActivityTransferSafeguard string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ProcessingActivityTransferSafeguardsStandardContractualClauses ProcessingActivityTransferSafeguards = "STANDARD_CONTRACTUAL_CLAUSES"
|
ProcessingActivityTransferSafeguardStandardContractualClauses ProcessingActivityTransferSafeguard = "STANDARD_CONTRACTUAL_CLAUSES"
|
||||||
ProcessingActivityTransferSafeguardsBindingCorporateRules ProcessingActivityTransferSafeguards = "BINDING_CORPORATE_RULES"
|
ProcessingActivityTransferSafeguardBindingCorporateRules ProcessingActivityTransferSafeguard = "BINDING_CORPORATE_RULES"
|
||||||
ProcessingActivityTransferSafeguardsAdequacyDecision ProcessingActivityTransferSafeguards = "ADEQUACY_DECISION"
|
ProcessingActivityTransferSafeguardAdequacyDecision ProcessingActivityTransferSafeguard = "ADEQUACY_DECISION"
|
||||||
ProcessingActivityTransferSafeguardsDerogations ProcessingActivityTransferSafeguards = "DEROGATIONS"
|
ProcessingActivityTransferSafeguardDerogations ProcessingActivityTransferSafeguard = "DEROGATIONS"
|
||||||
ProcessingActivityTransferSafeguardsCodesOfConduct ProcessingActivityTransferSafeguards = "CODES_OF_CONDUCT"
|
ProcessingActivityTransferSafeguardCodesOfConduct ProcessingActivityTransferSafeguard = "CODES_OF_CONDUCT"
|
||||||
ProcessingActivityTransferSafeguardsCertificationMechanisms ProcessingActivityTransferSafeguards = "CERTIFICATION_MECHANISMS"
|
ProcessingActivityTransferSafeguardCertificationMechanisms ProcessingActivityTransferSafeguard = "CERTIFICATION_MECHANISMS"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p ProcessingActivityTransferSafeguards) String() string {
|
func ProcessingActivityTransferSafeguards() []ProcessingActivityTransferSafeguard {
|
||||||
|
return []ProcessingActivityTransferSafeguard{
|
||||||
|
ProcessingActivityTransferSafeguardStandardContractualClauses,
|
||||||
|
ProcessingActivityTransferSafeguardBindingCorporateRules,
|
||||||
|
ProcessingActivityTransferSafeguardAdequacyDecision,
|
||||||
|
ProcessingActivityTransferSafeguardDerogations,
|
||||||
|
ProcessingActivityTransferSafeguardCodesOfConduct,
|
||||||
|
ProcessingActivityTransferSafeguardCertificationMechanisms,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProcessingActivityTransferSafeguard) String() string {
|
||||||
return string(p)
|
return string(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProcessingActivityTransferSafeguards) Scan(value any) error {
|
func (p *ProcessingActivityTransferSafeguard) Scan(value any) error {
|
||||||
var s string
|
var s string
|
||||||
switch v := value.(type) {
|
switch v := value.(type) {
|
||||||
case string:
|
case string:
|
||||||
@@ -42,28 +53,28 @@ func (p *ProcessingActivityTransferSafeguards) Scan(value any) error {
|
|||||||
case []byte:
|
case []byte:
|
||||||
s = string(v)
|
s = string(v)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported type for ProcessingActivityTransferSafeguards: %T", value)
|
return fmt.Errorf("unsupported type for ProcessingActivityTransferSafeguard: %T", value)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch s {
|
switch s {
|
||||||
case "STANDARD_CONTRACTUAL_CLAUSES":
|
case "STANDARD_CONTRACTUAL_CLAUSES":
|
||||||
*p = ProcessingActivityTransferSafeguardsStandardContractualClauses
|
*p = ProcessingActivityTransferSafeguardStandardContractualClauses
|
||||||
case "BINDING_CORPORATE_RULES":
|
case "BINDING_CORPORATE_RULES":
|
||||||
*p = ProcessingActivityTransferSafeguardsBindingCorporateRules
|
*p = ProcessingActivityTransferSafeguardBindingCorporateRules
|
||||||
case "ADEQUACY_DECISION":
|
case "ADEQUACY_DECISION":
|
||||||
*p = ProcessingActivityTransferSafeguardsAdequacyDecision
|
*p = ProcessingActivityTransferSafeguardAdequacyDecision
|
||||||
case "DEROGATIONS":
|
case "DEROGATIONS":
|
||||||
*p = ProcessingActivityTransferSafeguardsDerogations
|
*p = ProcessingActivityTransferSafeguardDerogations
|
||||||
case "CODES_OF_CONDUCT":
|
case "CODES_OF_CONDUCT":
|
||||||
*p = ProcessingActivityTransferSafeguardsCodesOfConduct
|
*p = ProcessingActivityTransferSafeguardCodesOfConduct
|
||||||
case "CERTIFICATION_MECHANISMS":
|
case "CERTIFICATION_MECHANISMS":
|
||||||
*p = ProcessingActivityTransferSafeguardsCertificationMechanisms
|
*p = ProcessingActivityTransferSafeguardCertificationMechanisms
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid ProcessingActivityTransferSafeguards value: %q", s)
|
return fmt.Errorf("invalid ProcessingActivityTransferSafeguard value: %q", s)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ProcessingActivityTransferSafeguards) Value() (driver.Value, error) {
|
func (p ProcessingActivityTransferSafeguard) Value() (driver.Value, error) {
|
||||||
return p.String(), nil
|
return p.String(), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,15 @@ const (
|
|||||||
RiskTreatmentTransferred RiskTreatment = "TRANSFERRED"
|
RiskTreatmentTransferred RiskTreatment = "TRANSFERRED"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func RiskTreatments() []RiskTreatment {
|
||||||
|
return []RiskTreatment{
|
||||||
|
RiskTreatmentMitigated,
|
||||||
|
RiskTreatmentAccepted,
|
||||||
|
RiskTreatmentAvoided,
|
||||||
|
RiskTreatmentTransferred,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (rt RiskTreatment) MarshalText() ([]byte, error) {
|
func (rt RiskTreatment) MarshalText() ([]byte, error) {
|
||||||
return []byte(rt.String()), nil
|
return []byte(rt.String()), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,16 @@ const (
|
|||||||
SnapshotsTypeProcessingActivities SnapshotsType = "PROCESSING_ACTIVITIES"
|
SnapshotsTypeProcessingActivities SnapshotsType = "PROCESSING_ACTIVITIES"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func SnapshotsTypes() []SnapshotsType {
|
||||||
|
return []SnapshotsType{
|
||||||
|
SnapshotsTypeRisks,
|
||||||
|
SnapshotsTypeVendors,
|
||||||
|
SnapshotsTypeAssets,
|
||||||
|
SnapshotsTypeData,
|
||||||
|
SnapshotsTypeNonconformities,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (st SnapshotsType) String() string {
|
func (st SnapshotsType) String() string {
|
||||||
return string(st)
|
return string(st)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,13 @@ const (
|
|||||||
TaskStateDone
|
TaskStateDone
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TaskStates() []TaskState {
|
||||||
|
return []TaskState{
|
||||||
|
TaskStateTodo,
|
||||||
|
TaskStateDone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ts TaskState) MarshalText() ([]byte, error) {
|
func (ts TaskState) MarshalText() ([]byte, error) {
|
||||||
return []byte(ts.String()), nil
|
return []byte(ts.String()), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ const (
|
|||||||
TrustCenterVisibilityPublic TrustCenterVisibility = "PUBLIC"
|
TrustCenterVisibilityPublic TrustCenterVisibility = "PUBLIC"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TrustCenterVisibilities() []TrustCenterVisibility {
|
||||||
|
return []TrustCenterVisibility{
|
||||||
|
TrustCenterVisibilityNone,
|
||||||
|
TrustCenterVisibilityPrivate,
|
||||||
|
TrustCenterVisibilityPublic,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (tcv TrustCenterVisibility) String() string {
|
func (tcv TrustCenterVisibility) String() string {
|
||||||
return string(tcv)
|
return string(tcv)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,33 @@ const (
|
|||||||
VendorCategoryVersionControl VendorCategory = "VERSION_CONTROL"
|
VendorCategoryVersionControl VendorCategory = "VERSION_CONTROL"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func VendorCategories() []VendorCategory {
|
||||||
|
return []VendorCategory{
|
||||||
|
VendorCategoryAnalytics,
|
||||||
|
VendorCategoryCloudMonitoring,
|
||||||
|
VendorCategoryCloudProvider,
|
||||||
|
VendorCategoryCollaboration,
|
||||||
|
VendorCategoryCustomerSupport,
|
||||||
|
VendorCategoryDataStorageAndProcessing,
|
||||||
|
VendorCategoryDocumentManagement,
|
||||||
|
VendorCategoryEmployeeManagement,
|
||||||
|
VendorCategoryEngineering,
|
||||||
|
VendorCategoryFinance,
|
||||||
|
VendorCategoryIdentityProvider,
|
||||||
|
VendorCategoryIT,
|
||||||
|
VendorCategoryMarketing,
|
||||||
|
VendorCategoryOfficeOperations,
|
||||||
|
VendorCategoryOther,
|
||||||
|
VendorCategoryPasswordManagement,
|
||||||
|
VendorCategoryProductAndDesign,
|
||||||
|
VendorCategoryProfessionalServices,
|
||||||
|
VendorCategoryRecruiting,
|
||||||
|
VendorCategorySales,
|
||||||
|
VendorCategorySecurity,
|
||||||
|
VendorCategoryVersionControl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (i VendorCategory) String() string {
|
func (i VendorCategory) String() string {
|
||||||
return string(i)
|
return string(i)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AssetService struct {
|
type AssetService struct {
|
||||||
@@ -35,6 +36,38 @@ type UpdateAssetRequest struct {
|
|||||||
VendorIDs []gid.GID
|
VendorIDs []gid.GID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (car *CreateAssetRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(car.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(car.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(car.Amount, "amount", validator.Required(), validator.Min(1))
|
||||||
|
v.Check(car.OwnerID, "owner_id", validator.Required(), validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(car.AssetType, "asset_type", validator.Required(), validator.OneOfSlice(coredata.AssetTypes()))
|
||||||
|
v.Check(car.DataTypesStored, "data_types_stored", validator.Required(), validator.SafeText(ContentMaxLength))
|
||||||
|
v.CheckEach(car.VendorIDs, "vendor_ids", func(index int, item any) {
|
||||||
|
v.Check(item, fmt.Sprintf("vendor_ids[%d]", index), validator.Required(), validator.GID(coredata.VendorEntityType))
|
||||||
|
})
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uar *UpdateAssetRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(uar.ID, "id", validator.Required(), validator.GID(coredata.AssetEntityType))
|
||||||
|
v.Check(uar.Name, "name", validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(uar.Amount, "amount", validator.Min(1))
|
||||||
|
v.Check(uar.OwnerID, "owner_id", validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(uar.AssetType, "asset_type", validator.OneOfSlice(coredata.AssetTypes()))
|
||||||
|
v.Check(uar.DataTypesStored, "data_types_stored", validator.SafeText(ContentMaxLength))
|
||||||
|
v.CheckEach(uar.VendorIDs, "vendor_ids", func(index int, item any) {
|
||||||
|
v.Check(item, fmt.Sprintf("vendor_ids[%d]", index), validator.GID(coredata.VendorEntityType))
|
||||||
|
})
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s AssetService) Get(
|
func (s AssetService) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
assetID gid.GID,
|
assetID gid.GID,
|
||||||
@@ -135,6 +168,10 @@ func (s AssetService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req UpdateAssetRequest,
|
req UpdateAssetRequest,
|
||||||
) (*coredata.Asset, error) {
|
) (*coredata.Asset, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
asset := &coredata.Asset{ID: req.ID}
|
asset := &coredata.Asset{ID: req.ID}
|
||||||
assetVendors := &coredata.AssetVendors{}
|
assetVendors := &coredata.AssetVendors{}
|
||||||
@@ -185,6 +222,10 @@ func (s AssetService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateAssetRequest,
|
req CreateAssetRequest,
|
||||||
) (*coredata.Asset, error) {
|
) (*coredata.Asset, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
assetID := gid.New(s.svc.scope.GetTenantID(), coredata.AssetEntityType)
|
assetID := gid.New(s.svc.scope.GetTenantID(), coredata.AssetEntityType)
|
||||||
assetVendors := &coredata.AssetVendors{}
|
assetVendors := &coredata.AssetVendors{}
|
||||||
|
|||||||
@@ -21,11 +21,12 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
"go.gearno.de/crypto/uuid"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/crypto/uuid"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuditService struct {
|
type AuditService struct {
|
||||||
@@ -52,21 +53,45 @@ type (
|
|||||||
TrustCenterVisibility *coredata.TrustCenterVisibility
|
TrustCenterVisibility *coredata.TrustCenterVisibility
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateAuditStateRequest struct {
|
|
||||||
ID gid.GID
|
|
||||||
State coredata.AuditState
|
|
||||||
}
|
|
||||||
|
|
||||||
UploadAuditReportRequest struct {
|
UploadAuditReportRequest struct {
|
||||||
AuditID gid.GID
|
AuditID gid.GID
|
||||||
File File
|
File File
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteAuditReportRequest struct {
|
|
||||||
ID gid.GID
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (car *CreateAuditRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(car.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(car.FrameworkID, "framework_id", validator.Required(), validator.GID(coredata.FrameworkEntityType))
|
||||||
|
v.Check(car.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(car.ValidUntil, "valid_until", validator.After(car.ValidFrom))
|
||||||
|
v.Check(car.State, "state", validator.OneOfSlice(coredata.AuditStates()))
|
||||||
|
v.Check(car.TrustCenterVisibility, "trust_center_visibility", validator.OneOfSlice(coredata.TrustCenterVisibilities()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uar *UpdateAuditRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(uar.ID, "id", validator.Required(), validator.GID(coredata.AuditEntityType))
|
||||||
|
v.Check(uar.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(uar.ValidUntil, "valid_until", validator.After(uar.ValidFrom))
|
||||||
|
v.Check(uar.State, "state", validator.OneOfSlice(coredata.AuditStates()))
|
||||||
|
v.Check(uar.TrustCenterVisibility, "trust_center_visibility", validator.OneOfSlice(coredata.TrustCenterVisibilities()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uarr *UploadAuditReportRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(uarr.AuditID, "audit_id", validator.Required(), validator.GID(coredata.AuditEntityType))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s AuditService) Get(
|
func (s AuditService) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
auditID gid.GID,
|
auditID gid.GID,
|
||||||
@@ -111,8 +136,11 @@ func (s *AuditService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *CreateAuditRequest,
|
req *CreateAuditRequest,
|
||||||
) (*coredata.Audit, error) {
|
) (*coredata.Audit, error) {
|
||||||
now := time.Now()
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
audit := &coredata.Audit{
|
audit := &coredata.Audit{
|
||||||
ID: gid.New(s.svc.scope.GetTenantID(), coredata.AuditEntityType),
|
ID: gid.New(s.svc.scope.GetTenantID(), coredata.AuditEntityType),
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
@@ -166,8 +194,11 @@ func (s *AuditService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *UpdateAuditRequest,
|
req *UpdateAuditRequest,
|
||||||
) (*coredata.Audit, error) {
|
) (*coredata.Audit, error) {
|
||||||
audit := &coredata.Audit{}
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
audit := &coredata.Audit{}
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
ctx,
|
ctx,
|
||||||
func(conn pg.Conn) error {
|
func(conn pg.Conn) error {
|
||||||
|
|||||||
@@ -22,11 +22,12 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/connector"
|
"go.probo.inc/probo/pkg/connector"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -55,6 +56,15 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (car *CreateConnectorRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
v.Check(car.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(car.Provider, "provider", validator.Required(), validator.OneOfSlice(coredata.ConnectorProviders()))
|
||||||
|
v.Check(car.Protocol, "protocol", validator.Required(), validator.OneOfSlice(coredata.ConnectorProtocols()))
|
||||||
|
v.Check(car.Connection, "connection", validator.Required())
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *ConnectorService) ListForOrganizationID(
|
func (s *ConnectorService) ListForOrganizationID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
organizationID gid.GID,
|
organizationID gid.GID,
|
||||||
@@ -88,20 +98,8 @@ func (s *ConnectorService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateConnectorRequest,
|
req CreateConnectorRequest,
|
||||||
) (*coredata.Connector, error) {
|
) (*coredata.Connector, error) {
|
||||||
if req.OrganizationID == gid.Nil {
|
if err := req.Validate(); err != nil {
|
||||||
return nil, fmt.Errorf("organization ID is required")
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
}
|
|
||||||
|
|
||||||
if req.Provider == "" {
|
|
||||||
return nil, fmt.Errorf("connector provider is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Protocol == "" {
|
|
||||||
return nil, fmt.Errorf("connector protocol is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.Connection == nil {
|
|
||||||
return nil, fmt.Errorf("connection configuration is required")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
id := gid.New(s.svc.scope.GetTenantID(), coredata.ConnectorEntityType)
|
id := gid.New(s.svc.scope.GetTenantID(), coredata.ConnectorEntityType)
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContinualImprovementService struct {
|
type ContinualImprovementService struct {
|
||||||
@@ -53,6 +54,34 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (ccir *CreateContinualImprovementRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(ccir.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(ccir.ReferenceID, "reference_id", validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(ccir.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(ccir.Source, "source", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(ccir.OwnerID, "owner_id", validator.Required(), validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(ccir.Status, "status", validator.OneOfSlice(coredata.ContinualImprovementStatuses()))
|
||||||
|
v.Check(ccir.Priority, "priority", validator.OneOfSlice(coredata.ContinualImprovementPriorities()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ucir *UpdateContinualImprovementRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(ucir.ID, "id", validator.Required(), validator.GID(coredata.ContinualImprovementEntityType))
|
||||||
|
v.Check(ucir.ReferenceID, "reference_id", validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(ucir.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(ucir.Source, "source", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(ucir.OwnerID, "owner_id", validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(ucir.Status, "status", validator.OneOfSlice(coredata.ContinualImprovementStatuses()))
|
||||||
|
v.Check(ucir.Priority, "priority", validator.OneOfSlice(coredata.ContinualImprovementPriorities()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s ContinualImprovementService) Get(
|
func (s ContinualImprovementService) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
continualImprovementID gid.GID,
|
continualImprovementID gid.GID,
|
||||||
@@ -81,6 +110,10 @@ func (s *ContinualImprovementService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *CreateContinualImprovementRequest,
|
req *CreateContinualImprovementRequest,
|
||||||
) (*coredata.ContinualImprovement, error) {
|
) (*coredata.ContinualImprovement, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
improvement := &coredata.ContinualImprovement{
|
improvement := &coredata.ContinualImprovement{
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -48,18 +49,35 @@ type (
|
|||||||
Status *coredata.ControlStatus
|
Status *coredata.ControlStatus
|
||||||
ExclusionJustification *string
|
ExclusionJustification *string
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectControlToMitigationRequest struct {
|
|
||||||
ControlID gid.GID
|
|
||||||
MitigationID gid.GID
|
|
||||||
}
|
|
||||||
|
|
||||||
DisconnectControlFromMitigationRequest struct {
|
|
||||||
ControlID gid.GID
|
|
||||||
MitigationID gid.GID
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (ccr *CreateControlRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(ccr.ID, "id", validator.Required(), validator.GID(coredata.ControlEntityType))
|
||||||
|
v.Check(ccr.FrameworkID, "framework_id", validator.Required(), validator.GID(coredata.FrameworkEntityType))
|
||||||
|
v.Check(ccr.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(ccr.Description, "description", validator.Required(), validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(ccr.SectionTitle, "section_title", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(ccr.Status, "status", validator.Required(), validator.OneOfSlice(coredata.ControlStatuses()))
|
||||||
|
v.Check(ccr.ExclusionJustification, "exclusion_justification", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ucr *UpdateControlRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(ucr.ID, "id", validator.Required(), validator.GID(coredata.ControlEntityType))
|
||||||
|
v.Check(ucr.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(ucr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(ucr.SectionTitle, "section_title", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(ucr.Status, "status", validator.OneOfSlice(coredata.ControlStatuses()))
|
||||||
|
v.Check(ucr.ExclusionJustification, "exclusion_justification", validator.SafeText(TitleMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s ControlService) CountForDocumentID(
|
func (s ControlService) CountForDocumentID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
documentID gid.GID,
|
documentID gid.GID,
|
||||||
@@ -706,6 +724,10 @@ func (s ControlService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateControlRequest,
|
req CreateControlRequest,
|
||||||
) (*coredata.Control, error) {
|
) (*coredata.Control, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
framework := &coredata.Framework{}
|
framework := &coredata.Framework{}
|
||||||
|
|
||||||
@@ -765,6 +787,10 @@ func (s ControlService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req UpdateControlRequest,
|
req UpdateControlRequest,
|
||||||
) (*coredata.Control, error) {
|
) (*coredata.Control, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
control := &coredata.Control{ID: req.ID}
|
control := &coredata.Control{ID: req.ID}
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
|
err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
|
||||||
|
|||||||
@@ -18,12 +18,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/log"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/certmanager"
|
"go.probo.inc/probo/pkg/certmanager"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/crypto/cipher"
|
"go.probo.inc/probo/pkg/crypto/cipher"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.gearno.de/kit/log"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -40,6 +41,15 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (ccdr *CreateCustomDomainRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(ccdr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(ccdr.Domain, "domain", validator.Required(), validator.NotEmpty(), validator.Domain())
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func NewCustomDomainService(
|
func NewCustomDomainService(
|
||||||
svc *TenantService,
|
svc *TenantService,
|
||||||
acmeService *certmanager.ACMEService,
|
acmeService *certmanager.ACMEService,
|
||||||
@@ -58,6 +68,10 @@ func (s *CustomDomainService) CreateCustomDomain(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateCustomDomainRequest,
|
req CreateCustomDomainRequest,
|
||||||
) (*coredata.CustomDomain, error) {
|
) (*coredata.CustomDomain, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
var domain *coredata.CustomDomain
|
var domain *coredata.CustomDomain
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
|
|||||||
@@ -19,30 +19,61 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DatumService struct {
|
type (
|
||||||
svc *TenantService
|
DatumService struct {
|
||||||
|
svc *TenantService
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateDatumRequest struct {
|
||||||
|
OrganizationID gid.GID
|
||||||
|
Name string
|
||||||
|
DataClassification coredata.DataClassification
|
||||||
|
OwnerID gid.GID
|
||||||
|
VendorIDs []gid.GID
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateDatumRequest struct {
|
||||||
|
ID gid.GID
|
||||||
|
Name *string
|
||||||
|
DataClassification *coredata.DataClassification
|
||||||
|
OwnerID *gid.GID
|
||||||
|
VendorIDs []gid.GID
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cdr *CreateDatumRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(cdr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(cdr.Name, "name", validator.Required(), validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(cdr.DataClassification, "data_classification", validator.Required(), validator.OneOfSlice(coredata.DataClassifications()))
|
||||||
|
v.Check(cdr.OwnerID, "owner_id", validator.Required(), validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.CheckEach(cdr.VendorIDs, "vendor_ids", func(index int, item any) {
|
||||||
|
v.Check(item, fmt.Sprintf("vendor_ids[%d]", index), validator.Required(), validator.GID(coredata.VendorEntityType))
|
||||||
|
})
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateDatumRequest struct {
|
func (udr *UpdateDatumRequest) Validate() error {
|
||||||
OrganizationID gid.GID
|
v := validator.New()
|
||||||
Name string
|
|
||||||
DataClassification coredata.DataClassification
|
|
||||||
OwnerID gid.GID
|
|
||||||
VendorIDs []gid.GID
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateDatumRequest struct {
|
v.Check(udr.ID, "id", validator.Required(), validator.GID(coredata.DatumEntityType))
|
||||||
ID gid.GID
|
v.Check(udr.Name, "name", validator.SafeText(NameMaxLength))
|
||||||
Name *string
|
v.Check(udr.DataClassification, "data_classification", validator.OneOfSlice(coredata.DataClassifications()))
|
||||||
DataClassification *coredata.DataClassification
|
v.Check(udr.OwnerID, "owner_id", validator.GID(coredata.PeopleEntityType))
|
||||||
OwnerID *gid.GID
|
v.CheckEach(udr.VendorIDs, "vendor_ids", func(index int, item any) {
|
||||||
VendorIDs []gid.GID
|
v.Check(item, fmt.Sprintf("vendor_ids[%d]", index), validator.Required(), validator.GID(coredata.VendorEntityType))
|
||||||
|
})
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s DatumService) Get(
|
func (s DatumService) Get(
|
||||||
@@ -145,6 +176,10 @@ func (s DatumService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req UpdateDatumRequest,
|
req UpdateDatumRequest,
|
||||||
) (*coredata.Datum, error) {
|
) (*coredata.Datum, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
datum := &coredata.Datum{}
|
datum := &coredata.Datum{}
|
||||||
datumVendors := &coredata.DatumVendors{}
|
datumVendors := &coredata.DatumVendors{}
|
||||||
@@ -189,6 +224,10 @@ func (s DatumService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateDatumRequest,
|
req CreateDatumRequest,
|
||||||
) (*coredata.Datum, error) {
|
) (*coredata.Datum, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
datumID := gid.New(s.svc.scope.GetTenantID(), coredata.DatumEntityType)
|
datumID := gid.New(s.svc.scope.GetTenantID(), coredata.DatumEntityType)
|
||||||
datumVendors := &coredata.DatumVendors{}
|
datumVendors := &coredata.DatumVendors{}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/html2pdf"
|
"go.probo.inc/probo/pkg/html2pdf"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.probo.inc/probo/pkg/statelesstoken"
|
"go.probo.inc/probo/pkg/statelesstoken"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
"go.probo.inc/probo/pkg/watermarkpdf"
|
"go.probo.inc/probo/pkg/watermarkpdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,6 +87,42 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (cdr *CreateDocumentRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(cdr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(cdr.Title, "title", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cdr.Content, "content", validator.Required(), validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(cdr.OwnerID, "owner_id", validator.Required(), validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(cdr.Classification, "classification", validator.Required(), validator.OneOfSlice(coredata.DocumentClassifications()))
|
||||||
|
v.Check(cdr.DocumentType, "document_type", validator.Required(), validator.OneOfSlice(coredata.DocumentTypes()))
|
||||||
|
v.Check(cdr.TrustCenterVisibility, "trust_center_visibility", validator.OneOfSlice(coredata.TrustCenterVisibilities()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (udr *UpdateDocumentRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(udr.DocumentID, "document_id", validator.Required(), validator.GID(coredata.DocumentEntityType))
|
||||||
|
v.Check(udr.Title, "title", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(udr.OwnerID, "owner_id", validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(udr.Classification, "classification", validator.OneOfSlice(coredata.DocumentClassifications()))
|
||||||
|
v.Check(udr.DocumentType, "document_type", validator.OneOfSlice(coredata.DocumentTypes()))
|
||||||
|
v.Check(udr.TrustCenterVisibility, "trust_center_visibility", validator.OneOfSlice(coredata.TrustCenterVisibilities()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (udvr *UpdateDocumentVersionRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(udvr.ID, "id", validator.Required(), validator.GID(coredata.DocumentVersionEntityType))
|
||||||
|
v.Check(udvr.Content, "content", validator.Required(), validator.SafeText(ContentMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TokenTypeSigningRequest = "signing_request"
|
TokenTypeSigningRequest = "signing_request"
|
||||||
|
|
||||||
@@ -308,6 +345,10 @@ func (s *DocumentService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateDocumentRequest,
|
req CreateDocumentRequest,
|
||||||
) (*coredata.Document, *coredata.DocumentVersion, error) {
|
) (*coredata.Document, *coredata.DocumentVersion, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
documentID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentEntityType)
|
documentID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentEntityType)
|
||||||
documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType)
|
documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType)
|
||||||
@@ -1131,6 +1172,10 @@ func (s *DocumentService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req UpdateDocumentRequest,
|
req UpdateDocumentRequest,
|
||||||
) (*coredata.Document, error) {
|
) (*coredata.Document, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
document := &coredata.Document{}
|
document := &coredata.Document{}
|
||||||
people := &coredata.People{}
|
people := &coredata.People{}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/crypto/uuid"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/filevalidation"
|
"go.probo.inc/probo/pkg/filevalidation"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/crypto/uuid"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -40,6 +41,16 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (umer *UploadMeasureEvidenceRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(umer.MeasureID, "measure_id", validator.Required(), validator.GID(coredata.MeasureEntityType))
|
||||||
|
v.Check(umer.URL, "url", validator.URL())
|
||||||
|
v.Check(umer.File, "file", validator.Required())
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s EvidenceService) Get(
|
func (s EvidenceService) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
evidenceID gid.GID,
|
evidenceID gid.GID,
|
||||||
@@ -68,6 +79,10 @@ func (s EvidenceService) UploadMeasureEvidence(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req UploadMeasureEvidenceRequest,
|
req UploadMeasureEvidenceRequest,
|
||||||
) (*coredata.Evidence, error) {
|
) (*coredata.Evidence, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
evidenceID := gid.New(s.svc.scope.GetTenantID(), coredata.EvidenceEntityType)
|
evidenceID := gid.New(s.svc.scope.GetTenantID(), coredata.EvidenceEntityType)
|
||||||
|
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
"go.gearno.de/crypto/uuid"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/filevalidation"
|
"go.probo.inc/probo/pkg/filevalidation"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.gearno.de/crypto/uuid"
|
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.probo.inc/probo/pkg/slug"
|
"go.probo.inc/probo/pkg/slug"
|
||||||
"go.probo.inc/probo/pkg/soagen"
|
"go.probo.inc/probo/pkg/soagen"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -73,6 +74,26 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (cfr *CreateFrameworkRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(cfr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(cfr.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cfr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ufr *UpdateFrameworkRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(ufr.ID, "id", validator.Required(), validator.GID(coredata.FrameworkEntityType))
|
||||||
|
v.Check(ufr.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(ufr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s FrameworkService) RequestExport(
|
func (s FrameworkService) RequestExport(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
frameworkID gid.GID,
|
frameworkID gid.GID,
|
||||||
@@ -310,6 +331,10 @@ func (s FrameworkService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateFrameworkRequest,
|
req CreateFrameworkRequest,
|
||||||
) (*coredata.Framework, error) {
|
) (*coredata.Framework, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
organization := &coredata.Organization{}
|
organization := &coredata.Organization{}
|
||||||
|
|
||||||
@@ -420,6 +445,10 @@ func (s FrameworkService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req UpdateFrameworkRequest,
|
req UpdateFrameworkRequest,
|
||||||
) (*coredata.Framework, error) {
|
) (*coredata.Framework, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
framework := &coredata.Framework{ID: req.ID}
|
framework := &coredata.Framework{ID: req.ID}
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
|
err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -69,6 +70,29 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (cmr *CreateMeasureRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(cmr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(cmr.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cmr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(cmr.Category, "category", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (umr *UpdateMeasureRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(umr.ID, "id", validator.Required(), validator.GID(coredata.MeasureEntityType))
|
||||||
|
v.Check(umr.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(umr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(umr.Category, "category", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(umr.State, "state", validator.OneOfSlice(coredata.MeasureStates()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s MeasureService) CountForRiskID(
|
func (s MeasureService) CountForRiskID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
riskID gid.GID,
|
riskID gid.GID,
|
||||||
@@ -399,6 +423,10 @@ func (s MeasureService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req UpdateMeasureRequest,
|
req UpdateMeasureRequest,
|
||||||
) (*coredata.Measure, error) {
|
) (*coredata.Measure, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
measure := &coredata.Measure{ID: req.ID}
|
measure := &coredata.Measure{ID: req.ID}
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
@@ -444,6 +472,10 @@ func (s MeasureService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateMeasureRequest,
|
req CreateMeasureRequest,
|
||||||
) (*coredata.Measure, error) {
|
) (*coredata.Measure, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
var measure *coredata.Measure
|
var measure *coredata.Measure
|
||||||
organization := &coredata.Organization{}
|
organization := &coredata.Organization{}
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NonconformityService struct {
|
type NonconformityService struct {
|
||||||
@@ -59,6 +60,36 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (cnr *CreateNonconformityRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(cnr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(cnr.ReferenceID, "reference_id", validator.Required(), validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(cnr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(cnr.AuditID, "audit_id", validator.Required(), validator.GID(coredata.AuditEntityType))
|
||||||
|
v.Check(cnr.RootCause, "root_cause", validator.Required(), validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(cnr.CorrectiveAction, "corrective_action", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(cnr.OwnerID, "owner_id", validator.Required(), validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(cnr.Status, "status", validator.OneOfSlice(coredata.NonconformityStatuses()))
|
||||||
|
v.Check(cnr.EffectivenessCheck, "effectiveness_check", validator.SafeText(ContentMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (unr *UpdateNonconformityRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(unr.ID, "id", validator.Required(), validator.GID(coredata.NonconformityEntityType))
|
||||||
|
v.Check(unr.ReferenceID, "reference_id", validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(unr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(unr.RootCause, "root_cause", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(unr.CorrectiveAction, "corrective_action", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(unr.OwnerID, "owner_id", validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(unr.Status, "status", validator.OneOfSlice(coredata.NonconformityStatuses()))
|
||||||
|
v.Check(unr.EffectivenessCheck, "effectiveness_check", validator.SafeText(ContentMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
func (s NonconformityService) Get(
|
func (s NonconformityService) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
nonconformityID gid.GID,
|
nonconformityID gid.GID,
|
||||||
@@ -83,6 +114,10 @@ func (s *NonconformityService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *CreateNonconformityRequest,
|
req *CreateNonconformityRequest,
|
||||||
) (*coredata.Nonconformity, error) {
|
) (*coredata.Nonconformity, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
nonconformity := &coredata.Nonconformity{
|
nonconformity := &coredata.Nonconformity{
|
||||||
@@ -143,6 +178,10 @@ func (s *NonconformityService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *UpdateNonconformityRequest,
|
req *UpdateNonconformityRequest,
|
||||||
) (*coredata.Nonconformity, error) {
|
) (*coredata.Nonconformity, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
nonconformity := &coredata.Nonconformity{}
|
nonconformity := &coredata.Nonconformity{}
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ObligationService struct {
|
type ObligationService struct {
|
||||||
@@ -57,6 +58,36 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (cor *CreateObligationRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(cor.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(cor.Area, "area", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cor.Source, "source", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cor.Requirement, "requirement", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cor.ActionsToBeImplemented, "actions_to_be_implemented", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cor.Regulator, "regulator", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cor.OwnerID, "owner_id", validator.Required(), validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(cor.Status, "status", validator.OneOfSlice(coredata.ObligationStatuses()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uor *UpdateObligationRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(uor.ID, "id", validator.Required(), validator.GID(coredata.ObligationEntityType))
|
||||||
|
v.Check(uor.Area, "area", validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(uor.Source, "source", validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(uor.Requirement, "requirement", validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(uor.ActionsToBeImplemented, "actions_to_be_implemented", validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(uor.Regulator, "regulator", validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(uor.OwnerID, "owner_id", validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(uor.Status, "status", validator.OneOfSlice(coredata.ObligationStatuses()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s ObligationService) Get(
|
func (s ObligationService) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
obligationID gid.GID,
|
obligationID gid.GID,
|
||||||
@@ -85,6 +116,10 @@ func (s *ObligationService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *CreateObligationRequest,
|
req *CreateObligationRequest,
|
||||||
) (*coredata.Obligation, error) {
|
) (*coredata.Obligation, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
obligation := &coredata.Obligation{
|
obligation := &coredata.Obligation{
|
||||||
@@ -135,6 +170,10 @@ func (s *ObligationService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *UpdateObligationRequest,
|
req *UpdateObligationRequest,
|
||||||
) (*coredata.Obligation, error) {
|
) (*coredata.Obligation, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
obligation := &coredata.Obligation{}
|
obligation := &coredata.Obligation{}
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
|
|||||||
@@ -22,12 +22,13 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/crypto/uuid"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/filevalidation"
|
"go.probo.inc/probo/pkg/filevalidation"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/slug"
|
"go.probo.inc/probo/pkg/slug"
|
||||||
"go.gearno.de/crypto/uuid"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -74,10 +75,37 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (cor *CreateOrganizationRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(cor.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uor *UpdateOrganizationRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(uor.ID, "id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(uor.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(uor.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(uor.WebsiteURL, "website_url", validator.SafeText(2048))
|
||||||
|
v.Check(uor.Email, "email", validator.SafeText(255))
|
||||||
|
v.Check(uor.HeadquarterAddress, "headquarter_address", validator.SafeText(2048))
|
||||||
|
v.Check(uor.File, "file", validator.NotEmpty())
|
||||||
|
v.Check(uor.HorizontalLogoFile, "horizontal_logo_file", validator.NotEmpty())
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s OrganizationService) Create(
|
func (s OrganizationService) Create(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateOrganizationRequest,
|
req CreateOrganizationRequest,
|
||||||
) (*coredata.Organization, error) {
|
) (*coredata.Organization, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
organizationID := gid.New(s.svc.scope.GetTenantID(), coredata.OrganizationEntityType)
|
organizationID := gid.New(s.svc.scope.GetTenantID(), coredata.OrganizationEntityType)
|
||||||
|
|
||||||
@@ -154,6 +182,10 @@ func (s OrganizationService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req UpdateOrganizationRequest,
|
req UpdateOrganizationRequest,
|
||||||
) (*coredata.Organization, error) {
|
) (*coredata.Organization, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
organization := &coredata.Organization{}
|
organization := &coredata.Organization{}
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -30,17 +31,6 @@ type (
|
|||||||
svc *TenantService
|
svc *TenantService
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdatePeopleRequest struct {
|
|
||||||
ID gid.GID
|
|
||||||
Kind *coredata.PeopleKind
|
|
||||||
FullName *string
|
|
||||||
PrimaryEmailAddress *string
|
|
||||||
AdditionalEmailAddresses *[]string
|
|
||||||
Position **string
|
|
||||||
ContractStartDate **time.Time
|
|
||||||
ContractEndDate **time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
CreatePeopleRequest struct {
|
CreatePeopleRequest struct {
|
||||||
OrganizationID gid.GID
|
OrganizationID gid.GID
|
||||||
FullName string
|
FullName string
|
||||||
@@ -51,8 +41,53 @@ type (
|
|||||||
ContractStartDate *time.Time
|
ContractStartDate *time.Time
|
||||||
ContractEndDate *time.Time
|
ContractEndDate *time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdatePeopleRequest struct {
|
||||||
|
ID gid.GID
|
||||||
|
Kind *coredata.PeopleKind
|
||||||
|
FullName *string
|
||||||
|
PrimaryEmailAddress *string
|
||||||
|
AdditionalEmailAddresses *[]string
|
||||||
|
Position **string
|
||||||
|
ContractStartDate **time.Time
|
||||||
|
ContractEndDate **time.Time
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (cpr *CreatePeopleRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(cpr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(cpr.FullName, "full_name", validator.Required(), validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(cpr.PrimaryEmailAddress, "primary_email_address", validator.Required(), validator.NotEmpty(), validator.Email())
|
||||||
|
v.CheckEach(cpr.AdditionalEmailAddresses, "additional_email_addresses", func(index int, item any) {
|
||||||
|
v.Check(item, fmt.Sprintf("additional_email_addresses[%d]", index), validator.Required(), validator.NotEmpty(), validator.Email())
|
||||||
|
})
|
||||||
|
v.Check(cpr.Kind, "kind", validator.Required(), validator.OneOfSlice(coredata.PeopleKinds()))
|
||||||
|
v.Check(cpr.Position, "position", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cpr.ContractStartDate, "contract_start_date", validator.Before(cpr.ContractEndDate))
|
||||||
|
v.Check(cpr.ContractEndDate, "contract_end_date", validator.After(cpr.ContractStartDate))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (upr *UpdatePeopleRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(upr.ID, "id", validator.Required(), validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(upr.Kind, "kind", validator.OneOfSlice(coredata.PeopleKinds()))
|
||||||
|
v.Check(upr.FullName, "full_name", validator.Required(), validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(upr.PrimaryEmailAddress, "primary_email_address", validator.NotEmpty(), validator.Email())
|
||||||
|
v.CheckEach(upr.AdditionalEmailAddresses, "additional_email_addresses", func(index int, item any) {
|
||||||
|
v.Check(item, fmt.Sprintf("additional_email_addresses[%d]", index), validator.Required(), validator.NotEmpty(), validator.Email())
|
||||||
|
})
|
||||||
|
v.Check(upr.Position, "position", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(upr.ContractStartDate, "contract_start_date", validator.Before(upr.ContractEndDate))
|
||||||
|
v.Check(upr.ContractEndDate, "contract_end_date", validator.After(upr.ContractStartDate))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s PeopleService) Get(
|
func (s PeopleService) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
peopleID gid.GID,
|
peopleID gid.GID,
|
||||||
@@ -133,6 +168,10 @@ func (s PeopleService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req UpdatePeopleRequest,
|
req UpdatePeopleRequest,
|
||||||
) (*coredata.People, error) {
|
) (*coredata.People, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
people := &coredata.People{}
|
people := &coredata.People{}
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
@@ -191,10 +230,8 @@ func (s PeopleService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreatePeopleRequest,
|
req CreatePeopleRequest,
|
||||||
) (*coredata.People, error) {
|
) (*coredata.People, error) {
|
||||||
if req.ContractStartDate != nil && req.ContractEndDate != nil {
|
if err := req.Validate(); err != nil {
|
||||||
if req.ContractEndDate.Before(*req.ContractStartDate) {
|
return nil, fmt.Errorf("invalid request: %w", err)
|
||||||
return nil, fmt.Errorf("contract end date must be after or equal to start date")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProcessingActivityService struct {
|
type ProcessingActivityService struct {
|
||||||
@@ -36,13 +37,13 @@ type (
|
|||||||
Purpose *string
|
Purpose *string
|
||||||
DataSubjectCategory *string
|
DataSubjectCategory *string
|
||||||
PersonalDataCategory *string
|
PersonalDataCategory *string
|
||||||
SpecialOrCriminalData coredata.ProcessingActivitySpecialOrCriminalData
|
SpecialOrCriminalData coredata.ProcessingActivitySpecialOrCriminalDatum
|
||||||
ConsentEvidenceLink *string
|
ConsentEvidenceLink *string
|
||||||
LawfulBasis coredata.ProcessingActivityLawfulBasis
|
LawfulBasis coredata.ProcessingActivityLawfulBasis
|
||||||
Recipients *string
|
Recipients *string
|
||||||
Location *string
|
Location *string
|
||||||
InternationalTransfers bool
|
InternationalTransfers bool
|
||||||
TransferSafeguards *coredata.ProcessingActivityTransferSafeguards
|
TransferSafeguard *coredata.ProcessingActivityTransferSafeguard
|
||||||
RetentionPeriod *string
|
RetentionPeriod *string
|
||||||
SecurityMeasures *string
|
SecurityMeasures *string
|
||||||
DataProtectionImpactAssessment coredata.ProcessingActivityDataProtectionImpactAssessment
|
DataProtectionImpactAssessment coredata.ProcessingActivityDataProtectionImpactAssessment
|
||||||
@@ -56,13 +57,13 @@ type (
|
|||||||
Purpose **string
|
Purpose **string
|
||||||
DataSubjectCategory **string
|
DataSubjectCategory **string
|
||||||
PersonalDataCategory **string
|
PersonalDataCategory **string
|
||||||
SpecialOrCriminalData *coredata.ProcessingActivitySpecialOrCriminalData
|
SpecialOrCriminalData *coredata.ProcessingActivitySpecialOrCriminalDatum
|
||||||
ConsentEvidenceLink **string
|
ConsentEvidenceLink **string
|
||||||
LawfulBasis *coredata.ProcessingActivityLawfulBasis
|
LawfulBasis *coredata.ProcessingActivityLawfulBasis
|
||||||
Recipients **string
|
Recipients **string
|
||||||
Location **string
|
Location **string
|
||||||
InternationalTransfers *bool
|
InternationalTransfers *bool
|
||||||
TransferSafeguards **coredata.ProcessingActivityTransferSafeguards
|
TransferSafeguard **coredata.ProcessingActivityTransferSafeguard
|
||||||
RetentionPeriod **string
|
RetentionPeriod **string
|
||||||
SecurityMeasures **string
|
SecurityMeasures **string
|
||||||
DataProtectionImpactAssessment *coredata.ProcessingActivityDataProtectionImpactAssessment
|
DataProtectionImpactAssessment *coredata.ProcessingActivityDataProtectionImpactAssessment
|
||||||
@@ -71,6 +72,57 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (cpar *CreateProcessingActivityRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(cpar.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(cpar.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cpar.Purpose, "purpose", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cpar.DataSubjectCategory, "data_subject_category", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cpar.PersonalDataCategory, "personal_data_category", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cpar.SpecialOrCriminalData, "special_or_criminal_data", validator.Required(), validator.OneOfSlice(coredata.ProcessingActivitySpecialOrCriminalData()))
|
||||||
|
v.Check(cpar.ConsentEvidenceLink, "consent_evidence_link", validator.SafeText(2048))
|
||||||
|
v.Check(cpar.LawfulBasis, "lawful_basis", validator.Required(), validator.OneOfSlice(coredata.ProcessingActivityLawfulBases()))
|
||||||
|
v.Check(cpar.Recipients, "recipients", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cpar.Location, "location", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cpar.InternationalTransfers, "international_transfers", validator.Required())
|
||||||
|
v.Check(cpar.TransferSafeguard, "transfer_safeguard", validator.OneOfSlice(coredata.ProcessingActivityTransferSafeguards()))
|
||||||
|
v.Check(cpar.RetentionPeriod, "retention_period", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cpar.SecurityMeasures, "security_measures", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cpar.DataProtectionImpactAssessment, "data_protection_impact_assessment", validator.Required(), validator.OneOfSlice(coredata.ProcessingActivityDataProtectionImpactAssessments()))
|
||||||
|
v.Check(cpar.TransferImpactAssessment, "transfer_impact_assessment", validator.Required(), validator.OneOfSlice(coredata.ProcessingActivityTransferImpactAssessments()))
|
||||||
|
v.CheckEach(cpar.VendorIDs, "vendor_ids", func(index int, item any) {
|
||||||
|
v.Check(item, fmt.Sprintf("vendor_ids[%d]", index), validator.Required(), validator.GID(coredata.VendorEntityType))
|
||||||
|
})
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (upar *UpdateProcessingActivityRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(upar.ID, "id", validator.Required(), validator.GID(coredata.ProcessingActivityEntityType))
|
||||||
|
v.Check(upar.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(upar.Purpose, "purpose", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(upar.DataSubjectCategory, "data_subject_category", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(upar.PersonalDataCategory, "personal_data_category", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(upar.SpecialOrCriminalData, "special_or_criminal_data", validator.OneOfSlice(coredata.ProcessingActivitySpecialOrCriminalData()))
|
||||||
|
v.Check(upar.ConsentEvidenceLink, "consent_evidence_link", validator.SafeText(2048))
|
||||||
|
v.Check(upar.LawfulBasis, "lawful_basis", validator.OneOfSlice(coredata.ProcessingActivityLawfulBases()))
|
||||||
|
v.Check(upar.Recipients, "recipients", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(upar.Location, "location", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(upar.TransferSafeguard, "transfer_safeguards", validator.OneOfSlice(coredata.ProcessingActivityTransferSafeguards()))
|
||||||
|
v.Check(upar.RetentionPeriod, "retention_period", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(upar.SecurityMeasures, "security_measures", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(upar.DataProtectionImpactAssessment, "data_protection_impact_assessment", validator.OneOfSlice(coredata.ProcessingActivityDataProtectionImpactAssessments()))
|
||||||
|
v.Check(upar.TransferImpactAssessment, "transfer_impact_assessment", validator.OneOfSlice(coredata.ProcessingActivityTransferImpactAssessments()))
|
||||||
|
v.CheckEach(upar.VendorIDs, "vendor_ids", func(index int, item any) {
|
||||||
|
v.Check(item, fmt.Sprintf("vendor_ids[%d]", index), validator.GID(coredata.VendorEntityType))
|
||||||
|
})
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s ProcessingActivityService) Get(
|
func (s ProcessingActivityService) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
processingActivityID gid.GID,
|
processingActivityID gid.GID,
|
||||||
@@ -111,7 +163,7 @@ func (s *ProcessingActivityService) Create(
|
|||||||
Recipients: req.Recipients,
|
Recipients: req.Recipients,
|
||||||
Location: req.Location,
|
Location: req.Location,
|
||||||
InternationalTransfers: req.InternationalTransfers,
|
InternationalTransfers: req.InternationalTransfers,
|
||||||
TransferSafeguards: req.TransferSafeguards,
|
TransferSafeguard: req.TransferSafeguard,
|
||||||
RetentionPeriod: req.RetentionPeriod,
|
RetentionPeriod: req.RetentionPeriod,
|
||||||
SecurityMeasures: req.SecurityMeasures,
|
SecurityMeasures: req.SecurityMeasures,
|
||||||
DataProtectionImpactAssessment: req.DataProtectionImpactAssessment,
|
DataProtectionImpactAssessment: req.DataProtectionImpactAssessment,
|
||||||
@@ -193,8 +245,8 @@ func (s *ProcessingActivityService) Update(
|
|||||||
if req.InternationalTransfers != nil {
|
if req.InternationalTransfers != nil {
|
||||||
processingActivity.InternationalTransfers = *req.InternationalTransfers
|
processingActivity.InternationalTransfers = *req.InternationalTransfers
|
||||||
}
|
}
|
||||||
if req.TransferSafeguards != nil {
|
if req.TransferSafeguard != nil {
|
||||||
processingActivity.TransferSafeguards = *req.TransferSafeguards
|
processingActivity.TransferSafeguard = *req.TransferSafeguard
|
||||||
}
|
}
|
||||||
if req.RetentionPeriod != nil {
|
if req.RetentionPeriod != nil {
|
||||||
processingActivity.RetentionPeriod = *req.RetentionPeriod
|
processingActivity.RetentionPeriod = *req.RetentionPeriod
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -59,6 +60,42 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (crr *CreateRiskRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(crr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(crr.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(crr.Description, "description", validator.Required(), validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(crr.Category, "category", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(crr.Treatment, "treatment", validator.Required(), validator.OneOfSlice(coredata.RiskTreatments()))
|
||||||
|
v.Check(crr.OwnerID, "owner_id", validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(crr.InherentLikelihood, "inherent_likelihood", validator.Required(), validator.Min(1), validator.Max(5))
|
||||||
|
v.Check(crr.InherentImpact, "inherent_impact", validator.Required(), validator.Min(1), validator.Max(5))
|
||||||
|
v.Check(crr.ResidualLikelihood, "residual_likelihood", validator.Min(1), validator.Max(5))
|
||||||
|
v.Check(crr.ResidualImpact, "residual_impact", validator.Min(1), validator.Max(5))
|
||||||
|
v.Check(crr.Note, "note", validator.SafeText(TitleMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (urr *UpdateRiskRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(urr.ID, "id", validator.Required(), validator.GID(coredata.RiskEntityType))
|
||||||
|
v.Check(urr.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(urr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(urr.Category, "category", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(urr.Treatment, "treatment", validator.OneOfSlice(coredata.RiskTreatments()))
|
||||||
|
v.Check(urr.OwnerID, "owner_id", validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(urr.InherentLikelihood, "inherent_likelihood", validator.Min(1), validator.Max(5))
|
||||||
|
v.Check(urr.InherentImpact, "inherent_impact", validator.Min(1), validator.Max(5))
|
||||||
|
v.Check(urr.ResidualLikelihood, "residual_likelihood", validator.Min(1), validator.Max(5))
|
||||||
|
v.Check(urr.ResidualImpact, "residual_impact", validator.Min(1), validator.Max(5))
|
||||||
|
v.Check(urr.Note, "note", validator.SafeText(TitleMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s RiskService) CountForMeasureID(
|
func (s RiskService) CountForMeasureID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
measureID gid.GID,
|
measureID gid.GID,
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
"go.gearno.de/kit/log"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.gearno.de/x/ref"
|
||||||
"go.probo.inc/probo/pkg/agents"
|
"go.probo.inc/probo/pkg/agents"
|
||||||
"go.probo.inc/probo/pkg/auth"
|
"go.probo.inc/probo/pkg/auth"
|
||||||
"go.probo.inc/probo/pkg/authz"
|
"go.probo.inc/probo/pkg/authz"
|
||||||
@@ -30,9 +33,12 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/filevalidation"
|
"go.probo.inc/probo/pkg/filevalidation"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/html2pdf"
|
"go.probo.inc/probo/pkg/html2pdf"
|
||||||
"go.gearno.de/kit/log"
|
)
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
"go.gearno.de/x/ref"
|
const (
|
||||||
|
NameMaxLength = 100
|
||||||
|
TitleMaxLength = 1000
|
||||||
|
ContentMaxLength = 5000
|
||||||
)
|
)
|
||||||
|
|
||||||
type ExportService interface {
|
type ExportService interface {
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SnapshotService struct {
|
type SnapshotService struct {
|
||||||
@@ -45,6 +46,28 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (csr *CreateSnapshotRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(csr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(csr.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(csr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(csr.Type, "type", validator.Required(), validator.OneOfSlice(coredata.SnapshotsTypes()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (usr *UpdateSnapshotRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(usr.ID, "id", validator.Required(), validator.GID(coredata.SnapshotEntityType))
|
||||||
|
v.Check(usr.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(usr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(usr.Type, "type", validator.OneOfSlice(coredata.SnapshotsTypes()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SnapshotService) Get(
|
func (s *SnapshotService) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
snapshotID gid.GID,
|
snapshotID gid.GID,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -51,10 +52,39 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (ctr *CreateTaskRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(ctr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(ctr.MeasureID, "measure_id", validator.GID(coredata.MeasureEntityType))
|
||||||
|
v.Check(ctr.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(ctr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(ctr.TimeEstimate, "time_estimate", validator.RangeDuration(0, 1000*time.Hour))
|
||||||
|
v.Check(ctr.AssignedToID, "assigned_to_id", validator.GID(coredata.PeopleEntityType))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (utr *UpdateTaskRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(utr.TaskID, "task_id", validator.Required(), validator.GID(coredata.TaskEntityType))
|
||||||
|
v.Check(utr.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(utr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(utr.TimeEstimate, "time_estimate", validator.RangeDuration(0, 1000*time.Hour))
|
||||||
|
v.Check(utr.State, "state", validator.OneOfSlice(coredata.TaskStates()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s TaskService) Create(
|
func (s TaskService) Create(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateTaskRequest,
|
req CreateTaskRequest,
|
||||||
) (*coredata.Task, error) {
|
) (*coredata.Task, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
taskID := gid.New(s.svc.scope.GetTenantID(), coredata.TaskEntityType)
|
taskID := gid.New(s.svc.scope.GetTenantID(), coredata.TaskEntityType)
|
||||||
|
|
||||||
@@ -180,6 +210,9 @@ func (s TaskService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req UpdateTaskRequest,
|
req UpdateTaskRequest,
|
||||||
) (*coredata.Task, error) {
|
) (*coredata.Task, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
task := &coredata.Task{}
|
task := &coredata.Task{}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ package probo
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/mail"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -27,6 +26,7 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.probo.inc/probo/pkg/statelesstoken"
|
"go.probo.inc/probo/pkg/statelesstoken"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -49,16 +49,40 @@ type (
|
|||||||
TrustCenterFileIDs []gid.GID
|
TrustCenterFileIDs []gid.GID
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteTrustCenterAccessRequest struct {
|
|
||||||
ID gid.GID
|
|
||||||
}
|
|
||||||
|
|
||||||
TrustCenterAccessData struct {
|
TrustCenterAccessData struct {
|
||||||
TrustCenterID gid.GID `json:"trust_center_id"`
|
TrustCenterID gid.GID `json:"trust_center_id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (ctcar *CreateTrustCenterAccessRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(ctcar.TrustCenterID, "trust_center_id", validator.Required(), validator.GID(coredata.TrustCenterEntityType))
|
||||||
|
v.Check(ctcar.Email, "email", validator.Required(), validator.Email())
|
||||||
|
v.Check(ctcar.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (utcar *UpdateTrustCenterAccessRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(utcar.ID, "id", validator.Required(), validator.GID(coredata.TrustCenterAccessEntityType))
|
||||||
|
v.Check(utcar.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.CheckEach(utcar.DocumentIDs, "document_ids", func(index int, item any) {
|
||||||
|
v.Check(item, fmt.Sprintf("document_ids[%d]", index), validator.Required(), validator.GID(coredata.DocumentEntityType))
|
||||||
|
})
|
||||||
|
v.CheckEach(utcar.ReportIDs, "report_ids", func(index int, item any) {
|
||||||
|
v.Check(item, fmt.Sprintf("report_ids[%d]", index), validator.Required(), validator.GID(coredata.ReportEntityType))
|
||||||
|
})
|
||||||
|
v.CheckEach(utcar.TrustCenterFileIDs, "trust_center_file_ids", func(index int, item any) {
|
||||||
|
v.Check(item, fmt.Sprintf("trust_center_file_ids[%d]", index), validator.Required(), validator.GID(coredata.TrustCenterFileEntityType))
|
||||||
|
})
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s TrustCenterAccessService) ListForTrustCenterID(
|
func (s TrustCenterAccessService) ListForTrustCenterID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
trustCenterID gid.GID,
|
trustCenterID gid.GID,
|
||||||
@@ -239,18 +263,12 @@ func (s TrustCenterAccessService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *CreateTrustCenterAccessRequest,
|
req *CreateTrustCenterAccessRequest,
|
||||||
) (*coredata.TrustCenterAccess, error) {
|
) (*coredata.TrustCenterAccess, error) {
|
||||||
if _, err := mail.ParseAddress(req.Email); err != nil {
|
if err := req.Validate(); err != nil {
|
||||||
return nil, fmt.Errorf("invalid email address")
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
if req.Name == "" {
|
|
||||||
return nil, fmt.Errorf("name is required")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
var access *coredata.TrustCenterAccess
|
var access *coredata.TrustCenterAccess
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
ctx,
|
ctx,
|
||||||
func(tx pg.Conn) error {
|
func(tx pg.Conn) error {
|
||||||
@@ -285,14 +303,13 @@ func (s TrustCenterAccessService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *UpdateTrustCenterAccessRequest,
|
req *UpdateTrustCenterAccessRequest,
|
||||||
) (*coredata.TrustCenterAccess, error) {
|
) (*coredata.TrustCenterAccess, error) {
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
var access *coredata.TrustCenterAccess
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
if req.Name != nil && *req.Name == "" {
|
|
||||||
return nil, fmt.Errorf("name is required")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
var access *coredata.TrustCenterAccess
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
ctx,
|
ctx,
|
||||||
func(tx pg.Conn) error {
|
func(tx pg.Conn) error {
|
||||||
@@ -344,14 +361,14 @@ func (s TrustCenterAccessService) Update(
|
|||||||
|
|
||||||
func (s TrustCenterAccessService) Delete(
|
func (s TrustCenterAccessService) Delete(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *DeleteTrustCenterAccessRequest,
|
trustCenterAccessID gid.GID,
|
||||||
) error {
|
) error {
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
ctx,
|
ctx,
|
||||||
func(tx pg.Conn) error {
|
func(tx pg.Conn) error {
|
||||||
access := &coredata.TrustCenterAccess{}
|
access := &coredata.TrustCenterAccess{}
|
||||||
|
|
||||||
if err := access.LoadByID(ctx, tx, s.svc.scope, req.ID); err != nil {
|
if err := access.LoadByID(ctx, tx, s.svc.scope, trustCenterAccessID); err != nil {
|
||||||
return fmt.Errorf("cannot load trust center access: %w", err)
|
return fmt.Errorf("cannot load trust center access: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,13 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
|
"go.gearno.de/crypto/uuid"
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/filevalidation"
|
"go.probo.inc/probo/pkg/filevalidation"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/crypto/uuid"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
"go.gearno.de/kit/pg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -53,12 +54,31 @@ type (
|
|||||||
Category *string
|
Category *string
|
||||||
TrustCenterVisibility *coredata.TrustCenterVisibility
|
TrustCenterVisibility *coredata.TrustCenterVisibility
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteTrustCenterFileRequest struct {
|
|
||||||
ID gid.GID
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (ctcfr *CreateTrustCenterFileRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(ctcfr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(ctcfr.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(ctcfr.Category, "category", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(ctcfr.File, "file", validator.Required())
|
||||||
|
v.Check(ctcfr.TrustCenterVisibility, "trust_center_visibility", validator.Required(), validator.OneOfSlice(coredata.TrustCenterVisibilities()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (utcfr *UpdateTrustCenterFileRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(utcfr.ID, "id", validator.Required(), validator.GID(coredata.TrustCenterFileEntityType))
|
||||||
|
v.Check(utcfr.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(utcfr.Category, "category", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(utcfr.TrustCenterVisibility, "trust_center_visibility", validator.OneOfSlice(coredata.TrustCenterVisibilities()))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s TrustCenterFileService) ListForOrganizationID(
|
func (s TrustCenterFileService) ListForOrganizationID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
organizationID gid.GID,
|
organizationID gid.GID,
|
||||||
@@ -134,8 +154,8 @@ func (s TrustCenterFileService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *CreateTrustCenterFileRequest,
|
req *CreateTrustCenterFileRequest,
|
||||||
) (*coredata.TrustCenterFile, error) {
|
) (*coredata.TrustCenterFile, error) {
|
||||||
if req.Name == "" {
|
if err := req.Validate(); err != nil {
|
||||||
return nil, fmt.Errorf("name is required")
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate file
|
// Validate file
|
||||||
@@ -197,14 +217,14 @@ func (s TrustCenterFileService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *UpdateTrustCenterFileRequest,
|
req *UpdateTrustCenterFileRequest,
|
||||||
) (*coredata.TrustCenterFile, error) {
|
) (*coredata.TrustCenterFile, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
var file *coredata.TrustCenterFile
|
var file *coredata.TrustCenterFile
|
||||||
|
|
||||||
if req.Name != nil && *req.Name == "" {
|
|
||||||
return nil, fmt.Errorf("name is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
ctx,
|
ctx,
|
||||||
func(tx pg.Conn) error {
|
func(tx pg.Conn) error {
|
||||||
@@ -242,14 +262,14 @@ func (s TrustCenterFileService) Update(
|
|||||||
|
|
||||||
func (s TrustCenterFileService) Delete(
|
func (s TrustCenterFileService) Delete(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *DeleteTrustCenterFileRequest,
|
trustCenterFileID gid.GID,
|
||||||
) error {
|
) error {
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
ctx,
|
ctx,
|
||||||
func(tx pg.Conn) error {
|
func(tx pg.Conn) error {
|
||||||
file := &coredata.TrustCenterFile{}
|
file := &coredata.TrustCenterFile{}
|
||||||
|
|
||||||
if err := file.LoadByID(ctx, tx, s.svc.scope, req.ID); err != nil {
|
if err := file.LoadByID(ctx, tx, s.svc.scope, trustCenterFileID); err != nil {
|
||||||
return fmt.Errorf("cannot load trust center file: %w", err)
|
return fmt.Errorf("cannot load trust center file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import (
|
|||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -54,12 +55,30 @@ type (
|
|||||||
LogoFile *File
|
LogoFile *File
|
||||||
Rank *int
|
Rank *int
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteTrustCenterReferenceRequest struct {
|
|
||||||
ID gid.GID
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (ctcrr *CreateTrustCenterReferenceRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(ctcrr.TrustCenterID, "trust_center_id", validator.Required(), validator.GID(coredata.TrustCenterEntityType))
|
||||||
|
v.Check(ctcrr.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(ctcrr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(ctcrr.WebsiteURL, "website_url", validator.Required(), validator.SafeText(2048))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (utcrr *UpdateTrustCenterReferenceRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(utcrr.ID, "id", validator.Required(), validator.GID(coredata.TrustCenterReferenceEntityType))
|
||||||
|
v.Check(utcrr.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(utcrr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(utcrr.WebsiteURL, "website_url", validator.SafeText(2048))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s TrustCenterReferenceService) ListForTrustCenterID(
|
func (s TrustCenterReferenceService) ListForTrustCenterID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
trustCenterID gid.GID,
|
trustCenterID gid.GID,
|
||||||
@@ -132,12 +151,8 @@ func (s TrustCenterReferenceService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *CreateTrustCenterReferenceRequest,
|
req *CreateTrustCenterReferenceRequest,
|
||||||
) (*coredata.TrustCenterReference, error) {
|
) (*coredata.TrustCenterReference, error) {
|
||||||
if req.Name == "" {
|
if err := req.Validate(); err != nil {
|
||||||
return nil, fmt.Errorf("name is required")
|
return nil, err
|
||||||
}
|
|
||||||
|
|
||||||
if req.WebsiteURL == "" {
|
|
||||||
return nil, fmt.Errorf("website URL is required")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
@@ -185,19 +200,14 @@ func (s TrustCenterReferenceService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *UpdateTrustCenterReferenceRequest,
|
req *UpdateTrustCenterReferenceRequest,
|
||||||
) (*coredata.TrustCenterReference, error) {
|
) (*coredata.TrustCenterReference, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
var reference *coredata.TrustCenterReference
|
var reference *coredata.TrustCenterReference
|
||||||
var newFileID *gid.GID
|
var newFileID *gid.GID
|
||||||
|
|
||||||
if req.Name != nil && *req.Name == "" {
|
|
||||||
return nil, fmt.Errorf("name is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if req.WebsiteURL != nil && *req.WebsiteURL == "" {
|
|
||||||
return nil, fmt.Errorf("website URL is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
var logoKey string
|
var logoKey string
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
|
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
|
||||||
@@ -254,12 +264,12 @@ func (s TrustCenterReferenceService) Update(
|
|||||||
|
|
||||||
func (s TrustCenterReferenceService) Delete(
|
func (s TrustCenterReferenceService) Delete(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *DeleteTrustCenterReferenceRequest,
|
trustCenterReferenceID gid.GID,
|
||||||
) error {
|
) error {
|
||||||
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
|
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
|
||||||
reference := &coredata.TrustCenterReference{}
|
reference := &coredata.TrustCenterReference{}
|
||||||
|
|
||||||
if err := reference.LoadByID(ctx, tx, s.svc.scope, req.ID); err != nil {
|
if err := reference.LoadByID(ctx, tx, s.svc.scope, trustCenterReferenceID); err != nil {
|
||||||
return fmt.Errorf("cannot load trust center reference: %w", err)
|
return fmt.Errorf("cannot load trust center reference: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,11 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
|
||||||
"go.probo.inc/probo/pkg/gid"
|
|
||||||
"go.gearno.de/crypto/uuid"
|
"go.gearno.de/crypto/uuid"
|
||||||
"go.gearno.de/kit/pg"
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -48,12 +49,27 @@ type (
|
|||||||
File io.Reader
|
File io.Reader
|
||||||
FileName string
|
FileName string
|
||||||
}
|
}
|
||||||
|
|
||||||
DeleteTrustCenterNDARequest struct {
|
|
||||||
TrustCenterID gid.GID
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (utcr *UpdateTrustCenterRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(utcr.ID, "id", validator.Required(), validator.GID(coredata.TrustCenterEntityType))
|
||||||
|
v.Check(utcr.Slug, "slug", validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(utcr.NonDisclosureAgreementFileID, "non_disclosure_agreement_file_id", validator.GID(coredata.FileEntityType))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (utcndar *UploadTrustCenterNDARequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(utcndar.TrustCenterID, "trust_center_id", validator.Required(), validator.GID(coredata.TrustCenterEntityType))
|
||||||
|
v.Check(utcndar.FileName, "file_name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s TrustCenterService) Get(
|
func (s TrustCenterService) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
trustCenterID gid.GID,
|
trustCenterID gid.GID,
|
||||||
@@ -124,6 +140,10 @@ func (s TrustCenterService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *UpdateTrustCenterRequest,
|
req *UpdateTrustCenterRequest,
|
||||||
) (*coredata.TrustCenter, *coredata.File, error) {
|
) (*coredata.TrustCenter, *coredata.File, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var trustCenter *coredata.TrustCenter
|
var trustCenter *coredata.TrustCenter
|
||||||
var file *coredata.File
|
var file *coredata.File
|
||||||
|
|
||||||
@@ -170,6 +190,10 @@ func (s TrustCenterService) UploadNDA(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *UploadTrustCenterNDARequest,
|
req *UploadTrustCenterNDARequest,
|
||||||
) (*coredata.TrustCenter, *coredata.File, error) {
|
) (*coredata.TrustCenter, *coredata.File, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
objectKey, err := uuid.NewV7()
|
objectKey, err := uuid.NewV7()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("cannot generate object key: %w", err)
|
return nil, nil, fmt.Errorf("cannot generate object key: %w", err)
|
||||||
@@ -249,7 +273,7 @@ func (s TrustCenterService) UploadNDA(
|
|||||||
|
|
||||||
func (s TrustCenterService) DeleteNDA(
|
func (s TrustCenterService) DeleteNDA(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req *DeleteTrustCenterNDARequest,
|
trustCenterID gid.GID,
|
||||||
) (*coredata.TrustCenter, *coredata.File, error) {
|
) (*coredata.TrustCenter, *coredata.File, error) {
|
||||||
var trustCenter *coredata.TrustCenter
|
var trustCenter *coredata.TrustCenter
|
||||||
|
|
||||||
@@ -257,7 +281,7 @@ func (s TrustCenterService) DeleteNDA(
|
|||||||
ctx,
|
ctx,
|
||||||
func(conn pg.Conn) error {
|
func(conn pg.Conn) error {
|
||||||
trustCenter = &coredata.TrustCenter{}
|
trustCenter = &coredata.TrustCenter{}
|
||||||
if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, req.TrustCenterID); err != nil {
|
if err := trustCenter.LoadByID(ctx, conn, s.svc.scope, trustCenterID); err != nil {
|
||||||
return fmt.Errorf("cannot load trust center: %w", err)
|
return fmt.Errorf("cannot load trust center: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +300,7 @@ func (s TrustCenterService) DeleteNDA(
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return trustCenter, nil, nil // File is nil after deletion
|
return trustCenter, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s TrustCenterService) GenerateNDAFileURL(
|
func (s TrustCenterService) GenerateNDAFileURL(
|
||||||
|
|||||||
@@ -25,10 +25,11 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
|
||||||
"go.probo.inc/probo/pkg/gid"
|
|
||||||
"go.gearno.de/crypto/uuid"
|
"go.gearno.de/crypto/uuid"
|
||||||
"go.gearno.de/kit/pg"
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -49,6 +50,23 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (vbaacr *VendorBusinessAssociateAgreementCreateRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(vbaacr.FileName, "file_name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(vbaacr.ValidUntil, "valid_until", validator.After(vbaacr.ValidFrom))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vbaaur *VendorBusinessAssociateAgreementUpdateRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(vbaaur.ValidUntil, "valid_until", validator.After(vbaaur.ValidFrom))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s VendorBusinessAssociateAgreementService) GetByVendorID(
|
func (s VendorBusinessAssociateAgreementService) GetByVendorID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
vendorID gid.GID,
|
vendorID gid.GID,
|
||||||
@@ -85,6 +103,10 @@ func (s VendorBusinessAssociateAgreementService) Upload(
|
|||||||
vendorID gid.GID,
|
vendorID gid.GID,
|
||||||
req *VendorBusinessAssociateAgreementCreateRequest,
|
req *VendorBusinessAssociateAgreementCreateRequest,
|
||||||
) (*coredata.VendorBusinessAssociateAgreement, *coredata.File, error) {
|
) (*coredata.VendorBusinessAssociateAgreement, *coredata.File, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
objectKey, err := uuid.NewV7()
|
objectKey, err := uuid.NewV7()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("cannot generate object key: %w", err)
|
return nil, nil, fmt.Errorf("cannot generate object key: %w", err)
|
||||||
@@ -255,6 +277,10 @@ func (s VendorBusinessAssociateAgreementService) Update(
|
|||||||
vendorID gid.GID,
|
vendorID gid.GID,
|
||||||
req *VendorBusinessAssociateAgreementUpdateRequest,
|
req *VendorBusinessAssociateAgreementUpdateRequest,
|
||||||
) (*coredata.VendorBusinessAssociateAgreement, *coredata.File, error) {
|
) (*coredata.VendorBusinessAssociateAgreement, *coredata.File, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
existingAgreement := &coredata.VendorBusinessAssociateAgreement{}
|
existingAgreement := &coredata.VendorBusinessAssociateAgreement{}
|
||||||
file := &coredata.File{}
|
file := &coredata.File{}
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/filevalidation"
|
"go.probo.inc/probo/pkg/filevalidation"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -40,6 +41,14 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (vcrcr *VendorComplianceReportCreateRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(vcrcr.ReportName, "report_name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s VendorComplianceReportService) ListForVendorID(
|
func (s VendorComplianceReportService) ListForVendorID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
vendorID gid.GID,
|
vendorID gid.GID,
|
||||||
@@ -66,6 +75,10 @@ func (s VendorComplianceReportService) Upload(
|
|||||||
vendorID gid.GID,
|
vendorID gid.GID,
|
||||||
req *VendorComplianceReportCreateRequest,
|
req *VendorComplianceReportCreateRequest,
|
||||||
) (*coredata.VendorComplianceReport, error) {
|
) (*coredata.VendorComplianceReport, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
vendor, err := s.svc.Vendors.Get(ctx, vendorID)
|
vendor, err := s.svc.Vendors.Get(ctx, vendorID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot get vendor: %w", err)
|
return nil, fmt.Errorf("cannot get vendor: %w", err)
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -47,6 +48,30 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (cvcr *CreateVendorContactRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(cvcr.VendorID, "vendor_id", validator.Required(), validator.GID(coredata.VendorEntityType))
|
||||||
|
v.Check(cvcr.FullName, "full_name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cvcr.Email, "email", validator.Email())
|
||||||
|
v.Check(cvcr.Phone, "phone", validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(cvcr.Role, "role", validator.SafeText(TitleMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uvcr *UpdateVendorContactRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(uvcr.ID, "id", validator.Required(), validator.GID(coredata.VendorContactEntityType))
|
||||||
|
v.Check(uvcr.FullName, "full_name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(uvcr.Email, "email", validator.Email())
|
||||||
|
v.Check(uvcr.Phone, "phone", validator.SafeText(NameMaxLength))
|
||||||
|
v.Check(uvcr.Role, "role", validator.SafeText(TitleMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s VendorContactService) Get(
|
func (s VendorContactService) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
vendorContactID gid.GID,
|
vendorContactID gid.GID,
|
||||||
@@ -102,6 +127,10 @@ func (s VendorContactService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateVendorContactRequest,
|
req CreateVendorContactRequest,
|
||||||
) (*coredata.VendorContact, error) {
|
) (*coredata.VendorContact, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
vendorContact := &coredata.VendorContact{
|
vendorContact := &coredata.VendorContact{
|
||||||
ID: gid.New(s.svc.scope.GetTenantID(), coredata.VendorContactEntityType),
|
ID: gid.New(s.svc.scope.GetTenantID(), coredata.VendorContactEntityType),
|
||||||
@@ -136,6 +165,10 @@ func (s VendorContactService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req UpdateVendorContactRequest,
|
req UpdateVendorContactRequest,
|
||||||
) (*coredata.VendorContact, error) {
|
) (*coredata.VendorContact, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
vendorContact := &coredata.VendorContact{}
|
vendorContact := &coredata.VendorContact{}
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
|
|||||||
@@ -25,10 +25,11 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
|
||||||
"go.probo.inc/probo/pkg/gid"
|
|
||||||
"go.gearno.de/crypto/uuid"
|
"go.gearno.de/crypto/uuid"
|
||||||
"go.gearno.de/kit/pg"
|
"go.gearno.de/kit/pg"
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -49,6 +50,23 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (vdpacr *VendorDataPrivacyAgreementCreateRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(vdpacr.FileName, "file_name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(vdpacr.ValidUntil, "valid_until", validator.After(vdpacr.ValidFrom))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vdpaur *VendorDataPrivacyAgreementUpdateRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(vdpaur.ValidUntil, "valid_until", validator.After(vdpaur.ValidFrom))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s VendorDataPrivacyAgreementService) GetByVendorID(
|
func (s VendorDataPrivacyAgreementService) GetByVendorID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
vendorID gid.GID,
|
vendorID gid.GID,
|
||||||
@@ -85,6 +103,10 @@ func (s VendorDataPrivacyAgreementService) Upload(
|
|||||||
vendorID gid.GID,
|
vendorID gid.GID,
|
||||||
req *VendorDataPrivacyAgreementCreateRequest,
|
req *VendorDataPrivacyAgreementCreateRequest,
|
||||||
) (*coredata.VendorDataPrivacyAgreement, *coredata.File, error) {
|
) (*coredata.VendorDataPrivacyAgreement, *coredata.File, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
objectKey, err := uuid.NewV7()
|
objectKey, err := uuid.NewV7()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("cannot generate object key: %w", err)
|
return nil, nil, fmt.Errorf("cannot generate object key: %w", err)
|
||||||
@@ -253,6 +275,10 @@ func (s VendorDataPrivacyAgreementService) Update(
|
|||||||
vendorID gid.GID,
|
vendorID gid.GID,
|
||||||
req *VendorDataPrivacyAgreementUpdateRequest,
|
req *VendorDataPrivacyAgreementUpdateRequest,
|
||||||
) (*coredata.VendorDataPrivacyAgreement, *coredata.File, error) {
|
) (*coredata.VendorDataPrivacyAgreement, *coredata.File, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
existingAgreement := &coredata.VendorDataPrivacyAgreement{}
|
existingAgreement := &coredata.VendorDataPrivacyAgreement{}
|
||||||
file := &coredata.File{}
|
file := &coredata.File{}
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -91,6 +92,67 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (cvr *CreateVendorRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(cvr.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||||
|
v.Check(cvr.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cvr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(cvr.HeadquarterAddress, "headquarter_address", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(cvr.LegalName, "legal_name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cvr.WebsiteURL, "website_url", validator.SafeText(2048))
|
||||||
|
v.Check(cvr.Category, "category", validator.OneOfSlice(coredata.VendorCategories()))
|
||||||
|
v.Check(cvr.PrivacyPolicyURL, "privacy_policy_url", validator.SafeText(2048))
|
||||||
|
v.Check(cvr.ServiceLevelAgreementURL, "service_level_agreement_url", validator.SafeText(2048))
|
||||||
|
v.Check(cvr.DataProcessingAgreementURL, "data_processing_agreement_url", validator.SafeText(2048))
|
||||||
|
v.Check(cvr.BusinessAssociateAgreementURL, "business_associate_agreement_url", validator.SafeText(2048))
|
||||||
|
v.Check(cvr.SubprocessorsListURL, "subprocessors_list_url", validator.SafeText(2048))
|
||||||
|
v.Check(cvr.SecurityPageURL, "security_page_url", validator.SafeText(2048))
|
||||||
|
v.Check(cvr.TrustPageURL, "trust_page_url", validator.SafeText(2048))
|
||||||
|
v.Check(cvr.TermsOfServiceURL, "terms_of_service_url", validator.SafeText(2048))
|
||||||
|
v.Check(cvr.StatusPageURL, "status_page_url", validator.SafeText(2048))
|
||||||
|
v.Check(cvr.BusinessOwnerID, "business_owner_id", validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(cvr.SecurityOwnerID, "security_owner_id", validator.GID(coredata.PeopleEntityType))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uvr *UpdateVendorRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(uvr.ID, "id", validator.Required(), validator.GID(coredata.VendorEntityType))
|
||||||
|
v.Check(uvr.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(uvr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(uvr.HeadquarterAddress, "headquarter_address", validator.SafeText(ContentMaxLength))
|
||||||
|
v.Check(uvr.LegalName, "legal_name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(uvr.WebsiteURL, "website_url", validator.SafeText(2048))
|
||||||
|
v.Check(uvr.Category, "category", validator.OneOfSlice(coredata.VendorCategories()))
|
||||||
|
v.Check(uvr.PrivacyPolicyURL, "privacy_policy_url", validator.SafeText(2048))
|
||||||
|
v.Check(uvr.ServiceLevelAgreementURL, "service_level_agreement_url", validator.SafeText(2048))
|
||||||
|
v.Check(uvr.DataProcessingAgreementURL, "data_processing_agreement_url", validator.SafeText(2048))
|
||||||
|
v.Check(uvr.BusinessAssociateAgreementURL, "business_associate_agreement_url", validator.SafeText(2048))
|
||||||
|
v.Check(uvr.SubprocessorsListURL, "subprocessors_list_url", validator.SafeText(2048))
|
||||||
|
v.Check(uvr.SecurityPageURL, "security_page_url", validator.SafeText(2048))
|
||||||
|
v.Check(uvr.TrustPageURL, "trust_page_url", validator.SafeText(2048))
|
||||||
|
v.Check(uvr.TermsOfServiceURL, "terms_of_service_url", validator.SafeText(2048))
|
||||||
|
v.Check(uvr.StatusPageURL, "status_page_url", validator.SafeText(2048))
|
||||||
|
v.Check(uvr.BusinessOwnerID, "business_owner_id", validator.GID(coredata.PeopleEntityType))
|
||||||
|
v.Check(uvr.SecurityOwnerID, "security_owner_id", validator.GID(coredata.PeopleEntityType))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cvrar *CreateVendorRiskAssessmentRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(cvrar.VendorID, "vendor_id", validator.Required(), validator.GID(coredata.VendorEntityType))
|
||||||
|
v.Check(cvrar.DataSensitivity, "data_sensitivity", validator.Required(), validator.OneOfSlice(coredata.DataSensitivities()))
|
||||||
|
v.Check(cvrar.BusinessImpact, "business_impact", validator.Required(), validator.OneOfSlice(coredata.BusinessImpacts()))
|
||||||
|
v.Check(cvrar.Notes, "notes", validator.SafeText(ContentMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s VendorService) CountForOrganizationID(
|
func (s VendorService) CountForOrganizationID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
organizationID gid.GID,
|
organizationID gid.GID,
|
||||||
@@ -208,6 +270,10 @@ func (s VendorService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req UpdateVendorRequest,
|
req UpdateVendorRequest,
|
||||||
) (*coredata.Vendor, error) {
|
) (*coredata.Vendor, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
vendor := &coredata.Vendor{}
|
vendor := &coredata.Vendor{}
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
@@ -373,6 +439,10 @@ func (s VendorService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateVendorRequest,
|
req CreateVendorRequest,
|
||||||
) (*coredata.Vendor, error) {
|
) (*coredata.Vendor, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
vendor := &coredata.Vendor{
|
vendor := &coredata.Vendor{
|
||||||
ID: gid.New(s.svc.scope.GetTenantID(), coredata.VendorEntityType),
|
ID: gid.New(s.svc.scope.GetTenantID(), coredata.VendorEntityType),
|
||||||
@@ -542,6 +612,10 @@ func (s VendorService) CreateRiskAssessment(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateVendorRiskAssessmentRequest,
|
req CreateVendorRiskAssessmentRequest,
|
||||||
) (*coredata.VendorRiskAssessment, error) {
|
) (*coredata.VendorRiskAssessment, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
vendorRiskAssessmentID := gid.New(s.svc.scope.GetTenantID(), coredata.VendorRiskAssessmentEntityType)
|
vendorRiskAssessmentID := gid.New(s.svc.scope.GetTenantID(), coredata.VendorRiskAssessmentEntityType)
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/coredata"
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
"go.probo.inc/probo/pkg/page"
|
"go.probo.inc/probo/pkg/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -43,6 +44,26 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (cvsr *CreateVendorServiceRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(cvsr.VendorID, "vendor_id", validator.Required(), validator.GID(coredata.VendorEntityType))
|
||||||
|
v.Check(cvsr.Name, "name", validator.Required(), validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(cvsr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uvsr *UpdateVendorServiceRequest) Validate() error {
|
||||||
|
v := validator.New()
|
||||||
|
|
||||||
|
v.Check(uvsr.ID, "id", validator.Required(), validator.GID(coredata.VendorServiceEntityType))
|
||||||
|
v.Check(uvsr.Name, "name", validator.SafeText(TitleMaxLength))
|
||||||
|
v.Check(uvsr.Description, "description", validator.SafeText(ContentMaxLength))
|
||||||
|
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func (s VendorServiceService) Get(
|
func (s VendorServiceService) Get(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
vendorServiceID gid.GID,
|
vendorServiceID gid.GID,
|
||||||
@@ -98,6 +119,10 @@ func (s VendorServiceService) Create(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateVendorServiceRequest,
|
req CreateVendorServiceRequest,
|
||||||
) (*coredata.VendorService, error) {
|
) (*coredata.VendorService, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
vendorService := &coredata.VendorService{
|
vendorService := &coredata.VendorService{
|
||||||
ID: gid.New(s.svc.scope.GetTenantID(), coredata.VendorServiceEntityType),
|
ID: gid.New(s.svc.scope.GetTenantID(), coredata.VendorServiceEntityType),
|
||||||
@@ -130,6 +155,10 @@ func (s VendorServiceService) Update(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req UpdateVendorServiceRequest,
|
req UpdateVendorServiceRequest,
|
||||||
) (*coredata.VendorService, error) {
|
) (*coredata.VendorService, error) {
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
vendorService := &coredata.VendorService{}
|
vendorService := &coredata.VendorService{}
|
||||||
|
|
||||||
err := s.svc.pg.WithTx(
|
err := s.svc.pg.WithTx(
|
||||||
|
|||||||
@@ -43,25 +43,16 @@ enum OrderDirection
|
|||||||
enum MeasureState
|
enum MeasureState
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MeasureState") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.MeasureState") {
|
||||||
NOT_STARTED
|
NOT_STARTED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotStarted")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotStarted"
|
|
||||||
)
|
|
||||||
IN_PROGRESS
|
IN_PROGRESS
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateInProgress")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.MeasureStateInProgress"
|
|
||||||
)
|
|
||||||
NOT_APPLICABLE
|
NOT_APPLICABLE
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotApplicable")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotApplicable"
|
|
||||||
)
|
|
||||||
IMPLEMENTED
|
IMPLEMENTED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateImplemented")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.MeasureStateImplemented"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TaskState
|
enum TaskState @goModel(model: "go.probo.inc/probo/pkg/coredata.TaskState") {
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.TaskState") {
|
|
||||||
TODO @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateTodo")
|
TODO @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateTodo")
|
||||||
DONE @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateDone")
|
DONE @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateDone")
|
||||||
}
|
}
|
||||||
@@ -69,43 +60,27 @@ enum TaskState
|
|||||||
enum EvidenceState
|
enum EvidenceState
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.EvidenceState") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.EvidenceState") {
|
||||||
FULFILLED
|
FULFILLED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateFulfilled")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.EvidenceStateFulfilled"
|
|
||||||
)
|
|
||||||
REQUESTED
|
REQUESTED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateRequested")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.EvidenceStateRequested"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PeopleKind
|
enum PeopleKind @goModel(model: "go.probo.inc/probo/pkg/coredata.PeopleKind") {
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.PeopleKind") {
|
EMPLOYEE @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleKindEmployee")
|
||||||
EMPLOYEE
|
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleKindEmployee")
|
|
||||||
CONTRACTOR
|
CONTRACTOR
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleKindContractor")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.PeopleKindContractor"
|
|
||||||
)
|
|
||||||
SERVICE_ACCOUNT
|
SERVICE_ACCOUNT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleKindServiceAccount")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.PeopleKindServiceAccount"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum InvitationStatus
|
enum InvitationStatus
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationStatus") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationStatus") {
|
||||||
PENDING
|
PENDING
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusPending")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.InvitationStatusPending"
|
|
||||||
)
|
|
||||||
ACCEPTED
|
ACCEPTED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusAccepted")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.InvitationStatusAccepted"
|
|
||||||
)
|
|
||||||
EXPIRED
|
EXPIRED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusExpired")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.InvitationStatusExpired"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Role @goModel(model: "go.probo.inc/probo/pkg/coredata.Role") {
|
enum Role @goModel(model: "go.probo.inc/probo/pkg/coredata.Role") {
|
||||||
@@ -117,12 +92,9 @@ enum Role @goModel(model: "go.probo.inc/probo/pkg/coredata.Role") {
|
|||||||
|
|
||||||
enum DocumentStatus
|
enum DocumentStatus
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentStatus") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentStatus") {
|
||||||
DRAFT
|
DRAFT @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusDraft")
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusDraft")
|
|
||||||
PUBLISHED
|
PUBLISHED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusPublished")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DocumentStatusPublished"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EvidenceType
|
enum EvidenceType
|
||||||
@@ -134,49 +106,28 @@ enum EvidenceType
|
|||||||
enum RiskTreatment
|
enum RiskTreatment
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.RiskTreatment") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.RiskTreatment") {
|
||||||
MITIGATED
|
MITIGATED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentMitigated")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentMitigated"
|
|
||||||
)
|
|
||||||
ACCEPTED
|
ACCEPTED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAccepted")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAccepted"
|
AVOIDED @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAvoided")
|
||||||
)
|
|
||||||
AVOIDED
|
|
||||||
@goEnum(
|
|
||||||
value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAvoided"
|
|
||||||
)
|
|
||||||
TRANSFERRED
|
TRANSFERRED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentTransferred")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentTransferred"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AuditState
|
enum AuditState @goModel(model: "go.probo.inc/probo/pkg/coredata.AuditState") {
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.AuditState") {
|
|
||||||
NOT_STARTED
|
NOT_STARTED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateNotStarted")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.AuditStateNotStarted"
|
|
||||||
)
|
|
||||||
IN_PROGRESS
|
IN_PROGRESS
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateInProgress")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.AuditStateInProgress"
|
|
||||||
)
|
|
||||||
COMPLETED
|
COMPLETED
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateCompleted")
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateCompleted")
|
||||||
REJECTED
|
REJECTED @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateRejected")
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateRejected")
|
OUTDATED @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateOutdated")
|
||||||
OUTDATED
|
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateOutdated")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SAMLEnforcementPolicy
|
enum SAMLEnforcementPolicy
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicy") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicy"
|
OFF @goEnum(value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOff")
|
||||||
) {
|
|
||||||
OFF
|
|
||||||
@goEnum(
|
|
||||||
value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOff"
|
|
||||||
)
|
|
||||||
OPTIONAL
|
OPTIONAL
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOptional"
|
value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOptional"
|
||||||
@@ -190,21 +141,14 @@ enum SAMLEnforcementPolicy
|
|||||||
enum UserAuthMethod
|
enum UserAuthMethod
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.UserAuthMethod") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.UserAuthMethod") {
|
||||||
PASSWORD
|
PASSWORD
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.UserAuthMethodPassword")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.UserAuthMethodPassword"
|
SAML @goEnum(value: "go.probo.inc/probo/pkg/coredata.UserAuthMethodSAML")
|
||||||
)
|
|
||||||
SAML
|
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.UserAuthMethodSAML")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TrustCenterVisibility
|
enum TrustCenterVisibility
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibility") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibility"
|
|
||||||
) {
|
|
||||||
NONE
|
NONE
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityNone")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityNone"
|
|
||||||
)
|
|
||||||
PRIVATE
|
PRIVATE
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityPrivate"
|
value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityPrivate"
|
||||||
@@ -216,21 +160,14 @@ enum TrustCenterVisibility
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum NonconformityStatus
|
enum NonconformityStatus
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.NonconformityStatus") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.NonconformityStatus"
|
OPEN @goEnum(value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusOpen")
|
||||||
) {
|
|
||||||
OPEN
|
|
||||||
@goEnum(
|
|
||||||
value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusOpen"
|
|
||||||
)
|
|
||||||
IN_PROGRESS
|
IN_PROGRESS
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusInProgress"
|
value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusInProgress"
|
||||||
)
|
)
|
||||||
CLOSED
|
CLOSED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusClosed")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusClosed"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ObligationStatus
|
enum ObligationStatus
|
||||||
@@ -244,9 +181,7 @@ enum ObligationStatus
|
|||||||
value: "go.probo.inc/probo/pkg/coredata.ObligationStatusPartiallyCompliant"
|
value: "go.probo.inc/probo/pkg/coredata.ObligationStatusPartiallyCompliant"
|
||||||
)
|
)
|
||||||
COMPLIANT
|
COMPLIANT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ObligationStatusCompliant")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ObligationStatusCompliant"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ContinualImprovementStatus
|
enum ContinualImprovementStatus
|
||||||
@@ -285,21 +220,21 @@ enum ContinualImprovementPriority
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ProcessingActivitySpecialOrCriminalData
|
enum ProcessingActivitySpecialOrCriminalDatum
|
||||||
@goModel(
|
@goModel(
|
||||||
model: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalData"
|
model: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatum"
|
||||||
) {
|
) {
|
||||||
YES
|
YES
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDataYes"
|
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumYes"
|
||||||
)
|
)
|
||||||
NO
|
NO
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDataNo"
|
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumNo"
|
||||||
)
|
)
|
||||||
POSSIBLE
|
POSSIBLE
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDataPossible"
|
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumPossible"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,33 +268,33 @@ enum ProcessingActivityLawfulBasis
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ProcessingActivityTransferSafeguards
|
enum ProcessingActivityTransferSafeguard
|
||||||
@goModel(
|
@goModel(
|
||||||
model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguards"
|
model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguard"
|
||||||
) {
|
) {
|
||||||
STANDARD_CONTRACTUAL_CLAUSES
|
STANDARD_CONTRACTUAL_CLAUSES
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsStandardContractualClauses"
|
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardStandardContractualClauses"
|
||||||
)
|
)
|
||||||
BINDING_CORPORATE_RULES
|
BINDING_CORPORATE_RULES
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsBindingCorporateRules"
|
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardBindingCorporateRules"
|
||||||
)
|
)
|
||||||
ADEQUACY_DECISION
|
ADEQUACY_DECISION
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsAdequacyDecision"
|
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardAdequacyDecision"
|
||||||
)
|
)
|
||||||
DEROGATIONS
|
DEROGATIONS
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsDerogations"
|
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardDerogations"
|
||||||
)
|
)
|
||||||
CODES_OF_CONDUCT
|
CODES_OF_CONDUCT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsCodesOfConduct"
|
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardCodesOfConduct"
|
||||||
)
|
)
|
||||||
CERTIFICATION_MECHANISMS
|
CERTIFICATION_MECHANISMS
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsCertificationMechanisms"
|
value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardCertificationMechanisms"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,47 +330,29 @@ enum ProcessingActivityTransferImpactAssessment
|
|||||||
enum UserOrderField
|
enum UserOrderField
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.UserOrderField") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.UserOrderField") {
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.UserOrderFieldCreatedAt")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.UserOrderFieldCreatedAt"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PeopleOrderField
|
enum PeopleOrderField
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.PeopleOrderField") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.PeopleOrderField") {
|
||||||
FULL_NAME
|
FULL_NAME
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldFullName")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldFullName"
|
|
||||||
)
|
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldCreatedAt")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldCreatedAt"
|
KIND @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldKind")
|
||||||
)
|
|
||||||
KIND
|
|
||||||
@goEnum(
|
|
||||||
value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldKind"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum VendorOrderField
|
enum VendorOrderField
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.VendorOrderField") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.VendorOrderField") {
|
||||||
NAME
|
NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldName")
|
||||||
@goEnum(
|
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldName"
|
|
||||||
)
|
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldCreatedAt")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldCreatedAt"
|
|
||||||
)
|
|
||||||
UPDATED_AT
|
UPDATED_AT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldUpdatedAt")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldUpdatedAt"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum FrameworkOrderField
|
enum FrameworkOrderField
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.FrameworkOrderField") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.FrameworkOrderField"
|
|
||||||
) {
|
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.FrameworkOrderFieldCreatedAt"
|
value: "go.probo.inc/probo/pkg/coredata.FrameworkOrderFieldCreatedAt"
|
||||||
@@ -445,9 +362,7 @@ enum FrameworkOrderField
|
|||||||
enum ControlOrderField
|
enum ControlOrderField
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.ControlOrderField") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.ControlOrderField") {
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldCreatedAt")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldCreatedAt"
|
|
||||||
)
|
|
||||||
SECTION_TITLE
|
SECTION_TITLE
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldSectionTitle"
|
value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldSectionTitle"
|
||||||
@@ -457,13 +372,8 @@ enum ControlOrderField
|
|||||||
enum MeasureOrderField
|
enum MeasureOrderField
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.MeasureOrderField") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.MeasureOrderField") {
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldCreatedAt")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldCreatedAt"
|
NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldName")
|
||||||
)
|
|
||||||
NAME
|
|
||||||
@goEnum(
|
|
||||||
value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldName"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TaskOrderField
|
enum TaskOrderField
|
||||||
@@ -474,9 +384,7 @@ enum TaskOrderField
|
|||||||
enum DocumentOrderField
|
enum DocumentOrderField
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentOrderField") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentOrderField") {
|
||||||
TITLE
|
TITLE
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldTitle")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldTitle"
|
|
||||||
)
|
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldCreatedAt"
|
value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldCreatedAt"
|
||||||
@@ -490,23 +398,14 @@ enum DocumentOrderField
|
|||||||
enum RiskOrderField
|
enum RiskOrderField
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.RiskOrderField") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.RiskOrderField") {
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCreatedAt")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCreatedAt"
|
|
||||||
)
|
|
||||||
UPDATED_AT
|
UPDATED_AT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldUpdatedAt")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldUpdatedAt"
|
NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldName")
|
||||||
)
|
|
||||||
NAME
|
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldName")
|
|
||||||
CATEGORY
|
CATEGORY
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCategory")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCategory"
|
|
||||||
)
|
|
||||||
TREATMENT
|
TREATMENT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldTreatment")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldTreatment"
|
|
||||||
)
|
|
||||||
INHERENT_RISK_SCORE
|
INHERENT_RISK_SCORE
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldInherentRiskScore"
|
value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldInherentRiskScore"
|
||||||
@@ -541,9 +440,7 @@ enum VendorComplianceReportOrderField
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum VendorContactOrderField
|
enum VendorContactOrderField
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.VendorContactOrderField") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.VendorContactOrderField"
|
|
||||||
) {
|
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorContactOrderFieldCreatedAt"
|
value: "go.probo.inc/probo/pkg/coredata.VendorContactOrderFieldCreatedAt"
|
||||||
@@ -559,9 +456,7 @@ enum VendorContactOrderField
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum VendorServiceOrderField
|
enum VendorServiceOrderField
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderField") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderField"
|
|
||||||
) {
|
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderFieldCreatedAt"
|
value: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderFieldCreatedAt"
|
||||||
@@ -573,13 +468,9 @@ enum VendorServiceOrderField
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum OrganizationOrderField
|
enum OrganizationOrderField
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.OrganizationOrderField") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.OrganizationOrderField"
|
|
||||||
) {
|
|
||||||
NAME
|
NAME
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.OrganizationOrderFieldName")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.OrganizationOrderFieldName"
|
|
||||||
)
|
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.OrganizationOrderFieldCreatedAt"
|
value: "go.probo.inc/probo/pkg/coredata.OrganizationOrderFieldCreatedAt"
|
||||||
@@ -592,41 +483,25 @@ enum OrganizationOrderField
|
|||||||
|
|
||||||
enum DataSensitivity
|
enum DataSensitivity
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.DataSensitivity") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.DataSensitivity") {
|
||||||
NONE
|
NONE @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityNone")
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityNone")
|
LOW @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityLow")
|
||||||
LOW
|
MEDIUM @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityMedium")
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityLow")
|
HIGH @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityHigh")
|
||||||
MEDIUM
|
|
||||||
@goEnum(
|
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DataSensitivityMedium"
|
|
||||||
)
|
|
||||||
HIGH
|
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityHigh")
|
|
||||||
CRITICAL
|
CRITICAL
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityCritical")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DataSensitivityCritical"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum BusinessImpact
|
enum BusinessImpact
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.BusinessImpact") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.BusinessImpact") {
|
||||||
LOW @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactLow")
|
LOW @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactLow")
|
||||||
MEDIUM
|
MEDIUM @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactMedium")
|
||||||
@goEnum(
|
HIGH @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactHigh")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.BusinessImpactMedium"
|
|
||||||
)
|
|
||||||
HIGH
|
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactHigh")
|
|
||||||
CRITICAL
|
CRITICAL
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactCritical")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.BusinessImpactCritical"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DocumentVersionOrderField
|
enum DocumentVersionOrderField
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderField") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderField"
|
|
||||||
) {
|
|
||||||
VERSION
|
VERSION
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderFieldVersion"
|
value: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderFieldVersion"
|
||||||
@@ -893,9 +768,7 @@ enum CountryCode
|
|||||||
enum VendorCategory
|
enum VendorCategory
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.VendorCategory") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.VendorCategory") {
|
||||||
ANALYTICS
|
ANALYTICS
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics"
|
|
||||||
)
|
|
||||||
CLOUD_MONITORING
|
CLOUD_MONITORING
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudMonitoring"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudMonitoring"
|
||||||
@@ -925,28 +798,21 @@ enum VendorCategory
|
|||||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEmployeeManagement"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEmployeeManagement"
|
||||||
)
|
)
|
||||||
ENGINEERING
|
ENGINEERING
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering"
|
|
||||||
)
|
|
||||||
FINANCE
|
FINANCE
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance"
|
|
||||||
)
|
|
||||||
IDENTITY_PROVIDER
|
IDENTITY_PROVIDER
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIdentityProvider"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIdentityProvider"
|
||||||
)
|
)
|
||||||
IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIT")
|
IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIT")
|
||||||
MARKETING
|
MARKETING
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing"
|
|
||||||
)
|
|
||||||
OFFICE_OPERATIONS
|
OFFICE_OPERATIONS
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOfficeOperations"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOfficeOperations"
|
||||||
)
|
)
|
||||||
OTHER
|
OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther")
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther")
|
|
||||||
PASSWORD_MANAGEMENT
|
PASSWORD_MANAGEMENT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryPasswordManagement"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryPasswordManagement"
|
||||||
@@ -960,15 +826,10 @@ enum VendorCategory
|
|||||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProfessionalServices"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProfessionalServices"
|
||||||
)
|
)
|
||||||
RECRUITING
|
RECRUITING
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting"
|
SALES @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales")
|
||||||
)
|
|
||||||
SALES
|
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales")
|
|
||||||
SECURITY
|
SECURITY
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity"
|
|
||||||
)
|
|
||||||
VERSION_CONTROL
|
VERSION_CONTROL
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryVersionControl"
|
value: "go.probo.inc/probo/pkg/coredata.VendorCategoryVersionControl"
|
||||||
@@ -977,21 +838,15 @@ enum VendorCategory
|
|||||||
|
|
||||||
enum DocumentType
|
enum DocumentType
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentType") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentType") {
|
||||||
OTHER
|
OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther")
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther")
|
|
||||||
ISMS @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeISMS")
|
ISMS @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeISMS")
|
||||||
POLICY
|
POLICY @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy")
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy")
|
|
||||||
PROCEDURE
|
PROCEDURE
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DocumentClassification
|
enum DocumentClassification
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentClassification") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.DocumentClassification"
|
|
||||||
) {
|
|
||||||
PUBLIC
|
PUBLIC
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DocumentClassificationPublic"
|
value: "go.probo.inc/probo/pkg/coredata.DocumentClassificationPublic"
|
||||||
@@ -1010,34 +865,23 @@ enum DocumentClassification
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AssetType
|
enum AssetType @goModel(model: "go.probo.inc/probo/pkg/coredata.AssetType") {
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.AssetType") {
|
PHYSICAL @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypePhysical")
|
||||||
PHYSICAL
|
VIRTUAL @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypeVirtual")
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypePhysical")
|
|
||||||
VIRTUAL
|
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypeVirtual")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AssetOrderField
|
enum AssetOrderField
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.AssetOrderField") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.AssetOrderField") {
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldCreatedAt")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldCreatedAt"
|
AMOUNT @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldAmount")
|
||||||
)
|
|
||||||
AMOUNT
|
|
||||||
@goEnum(
|
|
||||||
value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldAmount"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DatumOrderField
|
enum DatumOrderField
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.DatumOrderField") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.DatumOrderField") {
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldCreatedAt")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldCreatedAt"
|
NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldName")
|
||||||
)
|
|
||||||
NAME
|
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldName")
|
|
||||||
DATA_CLASSIFICATION
|
DATA_CLASSIFICATION
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldDataClassification"
|
value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldDataClassification"
|
||||||
@@ -1047,59 +891,38 @@ enum DatumOrderField
|
|||||||
enum DataClassification
|
enum DataClassification
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.DataClassification") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.DataClassification") {
|
||||||
PUBLIC
|
PUBLIC
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DataClassificationPublic")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DataClassificationPublic"
|
|
||||||
)
|
|
||||||
INTERNAL
|
INTERNAL
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DataClassificationInternal")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DataClassificationInternal"
|
|
||||||
)
|
|
||||||
CONFIDENTIAL
|
CONFIDENTIAL
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DataClassificationConfidential"
|
value: "go.probo.inc/probo/pkg/coredata.DataClassificationConfidential"
|
||||||
)
|
)
|
||||||
SECRET
|
SECRET
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.DataClassificationSecret")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.DataClassificationSecret"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ControlStatus
|
enum ControlStatus
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.ControlStatus") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.ControlStatus") {
|
||||||
INCLUDED
|
INCLUDED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ControlStatusIncluded")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ControlStatusIncluded"
|
|
||||||
)
|
|
||||||
EXCLUDED
|
EXCLUDED
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ControlStatusExcluded")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ControlStatusExcluded"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AuditOrderField
|
enum AuditOrderField
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.AuditOrderField") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.AuditOrderField") {
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldCreatedAt")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldCreatedAt"
|
|
||||||
)
|
|
||||||
VALID_FROM
|
VALID_FROM
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidFrom")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidFrom"
|
|
||||||
)
|
|
||||||
VALID_UNTIL
|
VALID_UNTIL
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidUntil")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidUntil"
|
STATE @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldState")
|
||||||
)
|
|
||||||
STATE
|
|
||||||
@goEnum(
|
|
||||||
value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldState"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NonconformityOrderField
|
enum NonconformityOrderField
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.NonconformityOrderField") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.NonconformityOrderField"
|
|
||||||
) {
|
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.NonconformityOrderFieldCreatedAt"
|
value: "go.probo.inc/probo/pkg/coredata.NonconformityOrderFieldCreatedAt"
|
||||||
@@ -1123,9 +946,7 @@ enum NonconformityOrderField
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum ObligationOrderField
|
enum ObligationOrderField
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.ObligationOrderField") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.ObligationOrderField"
|
|
||||||
) {
|
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldCreatedAt"
|
value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldCreatedAt"
|
||||||
@@ -1139,9 +960,7 @@ enum ObligationOrderField
|
|||||||
value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldDueDate"
|
value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldDueDate"
|
||||||
)
|
)
|
||||||
STATUS
|
STATUS
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldStatus")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldStatus"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ContinualImprovementOrderField
|
enum ContinualImprovementOrderField
|
||||||
@@ -1227,9 +1046,7 @@ enum TrustCenterReferenceOrderField
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum TrustCenterFileOrderField
|
enum TrustCenterFileOrderField
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderField") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderField"
|
|
||||||
) {
|
|
||||||
NAME
|
NAME
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderFieldName"
|
value: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderFieldName"
|
||||||
@@ -1246,24 +1063,16 @@ enum TrustCenterFileOrderField
|
|||||||
|
|
||||||
enum SnapshotsType
|
enum SnapshotsType
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.SnapshotsType") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.SnapshotsType") {
|
||||||
RISKS
|
RISKS @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeRisks")
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeRisks")
|
VENDORS @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeVendors")
|
||||||
VENDORS
|
ASSETS @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeAssets")
|
||||||
@goEnum(
|
DATA @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeData")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeVendors"
|
|
||||||
)
|
|
||||||
ASSETS
|
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeAssets")
|
|
||||||
DATA
|
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeData")
|
|
||||||
NONCONFORMITIES
|
NONCONFORMITIES
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeNonconformities"
|
value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeNonconformities"
|
||||||
)
|
)
|
||||||
OBLIGATIONS
|
OBLIGATIONS
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeObligations")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeObligations"
|
|
||||||
)
|
|
||||||
CONTINUAL_IMPROVEMENTS
|
CONTINUAL_IMPROVEMENTS
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeContinualImprovements"
|
value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeContinualImprovements"
|
||||||
@@ -1280,20 +1089,12 @@ enum SnapshotOrderField
|
|||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldCreatedAt"
|
value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldCreatedAt"
|
||||||
)
|
)
|
||||||
NAME
|
NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldName")
|
||||||
@goEnum(
|
TYPE @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldType")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldName"
|
|
||||||
)
|
|
||||||
TYPE
|
|
||||||
@goEnum(
|
|
||||||
value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldType"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MembershipOrderField
|
enum MembershipOrderField
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipOrderField") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.MembershipOrderField"
|
|
||||||
) {
|
|
||||||
FULL_NAME
|
FULL_NAME
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldFullName"
|
value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldFullName"
|
||||||
@@ -1303,9 +1104,7 @@ enum MembershipOrderField
|
|||||||
value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldEmailAddress"
|
value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldEmailAddress"
|
||||||
)
|
)
|
||||||
ROLE
|
ROLE
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldRole")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldRole"
|
|
||||||
)
|
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldCreatedAt"
|
value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldCreatedAt"
|
||||||
@@ -1313,21 +1112,15 @@ enum MembershipOrderField
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum InvitationOrderField
|
enum InvitationOrderField
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationOrderField") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.InvitationOrderField"
|
|
||||||
) {
|
|
||||||
FULL_NAME
|
FULL_NAME
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldFullName"
|
value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldFullName"
|
||||||
)
|
)
|
||||||
EMAIL
|
EMAIL
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldEmail")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldEmail"
|
|
||||||
)
|
|
||||||
ROLE
|
ROLE
|
||||||
@goEnum(
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldRole")
|
||||||
value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldRole"
|
|
||||||
)
|
|
||||||
CREATED_AT
|
CREATED_AT
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldCreatedAt"
|
value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldCreatedAt"
|
||||||
@@ -2347,13 +2140,13 @@ type ProcessingActivity implements Node {
|
|||||||
purpose: String
|
purpose: String
|
||||||
dataSubjectCategory: String
|
dataSubjectCategory: String
|
||||||
personalDataCategory: String
|
personalDataCategory: String
|
||||||
specialOrCriminalData: ProcessingActivitySpecialOrCriminalData!
|
specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum!
|
||||||
consentEvidenceLink: String
|
consentEvidenceLink: String
|
||||||
lawfulBasis: ProcessingActivityLawfulBasis!
|
lawfulBasis: ProcessingActivityLawfulBasis!
|
||||||
recipients: String
|
recipients: String
|
||||||
location: String
|
location: String
|
||||||
internationalTransfers: Boolean!
|
internationalTransfers: Boolean!
|
||||||
transferSafeguards: ProcessingActivityTransferSafeguards
|
transferSafeguards: ProcessingActivityTransferSafeguard
|
||||||
retentionPeriod: String
|
retentionPeriod: String
|
||||||
securityMeasures: String
|
securityMeasures: String
|
||||||
dataProtectionImpactAssessment: ProcessingActivityDataProtectionImpactAssessment!
|
dataProtectionImpactAssessment: ProcessingActivityDataProtectionImpactAssessment!
|
||||||
@@ -3920,13 +3713,13 @@ input CreateProcessingActivityInput {
|
|||||||
purpose: String
|
purpose: String
|
||||||
dataSubjectCategory: String
|
dataSubjectCategory: String
|
||||||
personalDataCategory: String
|
personalDataCategory: String
|
||||||
specialOrCriminalData: ProcessingActivitySpecialOrCriminalData!
|
specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum!
|
||||||
consentEvidenceLink: String
|
consentEvidenceLink: String
|
||||||
lawfulBasis: ProcessingActivityLawfulBasis!
|
lawfulBasis: ProcessingActivityLawfulBasis!
|
||||||
recipients: String
|
recipients: String
|
||||||
location: String
|
location: String
|
||||||
internationalTransfers: Boolean!
|
internationalTransfers: Boolean!
|
||||||
transferSafeguards: ProcessingActivityTransferSafeguards
|
transferSafeguards: ProcessingActivityTransferSafeguard
|
||||||
retentionPeriod: String
|
retentionPeriod: String
|
||||||
securityMeasures: String
|
securityMeasures: String
|
||||||
dataProtectionImpactAssessment: ProcessingActivityDataProtectionImpactAssessment!
|
dataProtectionImpactAssessment: ProcessingActivityDataProtectionImpactAssessment!
|
||||||
@@ -3940,13 +3733,13 @@ input UpdateProcessingActivityInput {
|
|||||||
purpose: String @goField(omittable: true)
|
purpose: String @goField(omittable: true)
|
||||||
dataSubjectCategory: String @goField(omittable: true)
|
dataSubjectCategory: String @goField(omittable: true)
|
||||||
personalDataCategory: String @goField(omittable: true)
|
personalDataCategory: String @goField(omittable: true)
|
||||||
specialOrCriminalData: ProcessingActivitySpecialOrCriminalData
|
specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum
|
||||||
consentEvidenceLink: String
|
consentEvidenceLink: String
|
||||||
lawfulBasis: ProcessingActivityLawfulBasis
|
lawfulBasis: ProcessingActivityLawfulBasis
|
||||||
recipients: String @goField(omittable: true)
|
recipients: String @goField(omittable: true)
|
||||||
location: String @goField(omittable: true)
|
location: String @goField(omittable: true)
|
||||||
internationalTransfers: Boolean
|
internationalTransfers: Boolean
|
||||||
transferSafeguards: ProcessingActivityTransferSafeguards
|
transferSafeguards: ProcessingActivityTransferSafeguard
|
||||||
@goField(omittable: true)
|
@goField(omittable: true)
|
||||||
retentionPeriod: String @goField(omittable: true)
|
retentionPeriod: String @goField(omittable: true)
|
||||||
securityMeasures: String @goField(omittable: true)
|
securityMeasures: String @goField(omittable: true)
|
||||||
@@ -4789,9 +4582,7 @@ type DeleteSnapshotPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum SSLStatus
|
enum SSLStatus
|
||||||
@goModel(
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatus") {
|
||||||
model: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatus"
|
|
||||||
) {
|
|
||||||
PENDING
|
PENDING
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusPending"
|
value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusPending"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -69,7 +69,7 @@ func NewProcessingActivity(par *coredata.ProcessingActivity) *ProcessingActivity
|
|||||||
Recipients: par.Recipients,
|
Recipients: par.Recipients,
|
||||||
Location: par.Location,
|
Location: par.Location,
|
||||||
InternationalTransfers: par.InternationalTransfers,
|
InternationalTransfers: par.InternationalTransfers,
|
||||||
TransferSafeguards: par.TransferSafeguards,
|
TransferSafeguards: par.TransferSafeguard,
|
||||||
RetentionPeriod: par.RetentionPeriod,
|
RetentionPeriod: par.RetentionPeriod,
|
||||||
SecurityMeasures: par.SecurityMeasures,
|
SecurityMeasures: par.SecurityMeasures,
|
||||||
DataProtectionImpactAssessment: par.DataProtectionImpactAssessment,
|
DataProtectionImpactAssessment: par.DataProtectionImpactAssessment,
|
||||||
|
|||||||
@@ -440,13 +440,13 @@ type CreateProcessingActivityInput struct {
|
|||||||
Purpose *string `json:"purpose,omitempty"`
|
Purpose *string `json:"purpose,omitempty"`
|
||||||
DataSubjectCategory *string `json:"dataSubjectCategory,omitempty"`
|
DataSubjectCategory *string `json:"dataSubjectCategory,omitempty"`
|
||||||
PersonalDataCategory *string `json:"personalDataCategory,omitempty"`
|
PersonalDataCategory *string `json:"personalDataCategory,omitempty"`
|
||||||
SpecialOrCriminalData coredata.ProcessingActivitySpecialOrCriminalData `json:"specialOrCriminalData"`
|
SpecialOrCriminalData coredata.ProcessingActivitySpecialOrCriminalDatum `json:"specialOrCriminalData"`
|
||||||
ConsentEvidenceLink *string `json:"consentEvidenceLink,omitempty"`
|
ConsentEvidenceLink *string `json:"consentEvidenceLink,omitempty"`
|
||||||
LawfulBasis coredata.ProcessingActivityLawfulBasis `json:"lawfulBasis"`
|
LawfulBasis coredata.ProcessingActivityLawfulBasis `json:"lawfulBasis"`
|
||||||
Recipients *string `json:"recipients,omitempty"`
|
Recipients *string `json:"recipients,omitempty"`
|
||||||
Location *string `json:"location,omitempty"`
|
Location *string `json:"location,omitempty"`
|
||||||
InternationalTransfers bool `json:"internationalTransfers"`
|
InternationalTransfers bool `json:"internationalTransfers"`
|
||||||
TransferSafeguards *coredata.ProcessingActivityTransferSafeguards `json:"transferSafeguards,omitempty"`
|
TransferSafeguard *coredata.ProcessingActivityTransferSafeguard `json:"transferSafeguards,omitempty"`
|
||||||
RetentionPeriod *string `json:"retentionPeriod,omitempty"`
|
RetentionPeriod *string `json:"retentionPeriod,omitempty"`
|
||||||
SecurityMeasures *string `json:"securityMeasures,omitempty"`
|
SecurityMeasures *string `json:"securityMeasures,omitempty"`
|
||||||
DataProtectionImpactAssessment coredata.ProcessingActivityDataProtectionImpactAssessment `json:"dataProtectionImpactAssessment"`
|
DataProtectionImpactAssessment coredata.ProcessingActivityDataProtectionImpactAssessment `json:"dataProtectionImpactAssessment"`
|
||||||
@@ -1518,13 +1518,13 @@ type ProcessingActivity struct {
|
|||||||
Purpose *string `json:"purpose,omitempty"`
|
Purpose *string `json:"purpose,omitempty"`
|
||||||
DataSubjectCategory *string `json:"dataSubjectCategory,omitempty"`
|
DataSubjectCategory *string `json:"dataSubjectCategory,omitempty"`
|
||||||
PersonalDataCategory *string `json:"personalDataCategory,omitempty"`
|
PersonalDataCategory *string `json:"personalDataCategory,omitempty"`
|
||||||
SpecialOrCriminalData coredata.ProcessingActivitySpecialOrCriminalData `json:"specialOrCriminalData"`
|
SpecialOrCriminalData coredata.ProcessingActivitySpecialOrCriminalDatum `json:"specialOrCriminalData"`
|
||||||
ConsentEvidenceLink *string `json:"consentEvidenceLink,omitempty"`
|
ConsentEvidenceLink *string `json:"consentEvidenceLink,omitempty"`
|
||||||
LawfulBasis coredata.ProcessingActivityLawfulBasis `json:"lawfulBasis"`
|
LawfulBasis coredata.ProcessingActivityLawfulBasis `json:"lawfulBasis"`
|
||||||
Recipients *string `json:"recipients,omitempty"`
|
Recipients *string `json:"recipients,omitempty"`
|
||||||
Location *string `json:"location,omitempty"`
|
Location *string `json:"location,omitempty"`
|
||||||
InternationalTransfers bool `json:"internationalTransfers"`
|
InternationalTransfers bool `json:"internationalTransfers"`
|
||||||
TransferSafeguards *coredata.ProcessingActivityTransferSafeguards `json:"transferSafeguards,omitempty"`
|
TransferSafeguards *coredata.ProcessingActivityTransferSafeguard `json:"transferSafeguards,omitempty"`
|
||||||
RetentionPeriod *string `json:"retentionPeriod,omitempty"`
|
RetentionPeriod *string `json:"retentionPeriod,omitempty"`
|
||||||
SecurityMeasures *string `json:"securityMeasures,omitempty"`
|
SecurityMeasures *string `json:"securityMeasures,omitempty"`
|
||||||
DataProtectionImpactAssessment coredata.ProcessingActivityDataProtectionImpactAssessment `json:"dataProtectionImpactAssessment"`
|
DataProtectionImpactAssessment coredata.ProcessingActivityDataProtectionImpactAssessment `json:"dataProtectionImpactAssessment"`
|
||||||
@@ -2022,23 +2022,23 @@ type UpdatePeoplePayload struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UpdateProcessingActivityInput struct {
|
type UpdateProcessingActivityInput struct {
|
||||||
ID gid.GID `json:"id"`
|
ID gid.GID `json:"id"`
|
||||||
Name *string `json:"name,omitempty"`
|
Name *string `json:"name,omitempty"`
|
||||||
Purpose graphql.Omittable[*string] `json:"purpose,omitempty"`
|
Purpose graphql.Omittable[*string] `json:"purpose,omitempty"`
|
||||||
DataSubjectCategory graphql.Omittable[*string] `json:"dataSubjectCategory,omitempty"`
|
DataSubjectCategory graphql.Omittable[*string] `json:"dataSubjectCategory,omitempty"`
|
||||||
PersonalDataCategory graphql.Omittable[*string] `json:"personalDataCategory,omitempty"`
|
PersonalDataCategory graphql.Omittable[*string] `json:"personalDataCategory,omitempty"`
|
||||||
SpecialOrCriminalData *coredata.ProcessingActivitySpecialOrCriminalData `json:"specialOrCriminalData,omitempty"`
|
SpecialOrCriminalData *coredata.ProcessingActivitySpecialOrCriminalDatum `json:"specialOrCriminalData,omitempty"`
|
||||||
ConsentEvidenceLink *string `json:"consentEvidenceLink,omitempty"`
|
ConsentEvidenceLink *string `json:"consentEvidenceLink,omitempty"`
|
||||||
LawfulBasis *coredata.ProcessingActivityLawfulBasis `json:"lawfulBasis,omitempty"`
|
LawfulBasis *coredata.ProcessingActivityLawfulBasis `json:"lawfulBasis,omitempty"`
|
||||||
Recipients graphql.Omittable[*string] `json:"recipients,omitempty"`
|
Recipients graphql.Omittable[*string] `json:"recipients,omitempty"`
|
||||||
Location graphql.Omittable[*string] `json:"location,omitempty"`
|
Location graphql.Omittable[*string] `json:"location,omitempty"`
|
||||||
InternationalTransfers *bool `json:"internationalTransfers,omitempty"`
|
InternationalTransfers *bool `json:"internationalTransfers,omitempty"`
|
||||||
TransferSafeguards graphql.Omittable[*coredata.ProcessingActivityTransferSafeguards] `json:"transferSafeguards,omitempty"`
|
TransferSafeguards graphql.Omittable[*coredata.ProcessingActivityTransferSafeguard] `json:"transferSafeguards,omitempty"`
|
||||||
RetentionPeriod graphql.Omittable[*string] `json:"retentionPeriod,omitempty"`
|
RetentionPeriod graphql.Omittable[*string] `json:"retentionPeriod,omitempty"`
|
||||||
SecurityMeasures graphql.Omittable[*string] `json:"securityMeasures,omitempty"`
|
SecurityMeasures graphql.Omittable[*string] `json:"securityMeasures,omitempty"`
|
||||||
DataProtectionImpactAssessment *coredata.ProcessingActivityDataProtectionImpactAssessment `json:"dataProtectionImpactAssessment,omitempty"`
|
DataProtectionImpactAssessment *coredata.ProcessingActivityDataProtectionImpactAssessment `json:"dataProtectionImpactAssessment,omitempty"`
|
||||||
TransferImpactAssessment *coredata.ProcessingActivityTransferImpactAssessment `json:"transferImpactAssessment,omitempty"`
|
TransferImpactAssessment *coredata.ProcessingActivityTransferImpactAssessment `json:"transferImpactAssessment,omitempty"`
|
||||||
VendorIds []gid.GID `json:"vendorIds,omitempty"`
|
VendorIds []gid.GID `json:"vendorIds,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateProcessingActivityPayload struct {
|
type UpdateProcessingActivityPayload struct {
|
||||||
|
|||||||
@@ -1384,9 +1384,7 @@ func (r *mutationResolver) UploadTrustCenterNda(ctx context.Context, input types
|
|||||||
func (r *mutationResolver) DeleteTrustCenterNda(ctx context.Context, input types.DeleteTrustCenterNDAInput) (*types.DeleteTrustCenterNDAPayload, error) {
|
func (r *mutationResolver) DeleteTrustCenterNda(ctx context.Context, input types.DeleteTrustCenterNDAInput) (*types.DeleteTrustCenterNDAPayload, error) {
|
||||||
prb := r.ProboService(ctx, input.TrustCenterID.TenantID())
|
prb := r.ProboService(ctx, input.TrustCenterID.TenantID())
|
||||||
|
|
||||||
trustCenter, file, err := prb.TrustCenters.DeleteNDA(ctx, &probo.DeleteTrustCenterNDARequest{
|
trustCenter, file, err := prb.TrustCenters.DeleteNDA(ctx, input.TrustCenterID)
|
||||||
TrustCenterID: input.TrustCenterID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("cannot delete trust center NDA: %w", err))
|
panic(fmt.Errorf("cannot delete trust center NDA: %w", err))
|
||||||
}
|
}
|
||||||
@@ -1443,9 +1441,7 @@ func (r *mutationResolver) UpdateTrustCenterAccess(ctx context.Context, input ty
|
|||||||
func (r *mutationResolver) DeleteTrustCenterAccess(ctx context.Context, input types.DeleteTrustCenterAccessInput) (*types.DeleteTrustCenterAccessPayload, error) {
|
func (r *mutationResolver) DeleteTrustCenterAccess(ctx context.Context, input types.DeleteTrustCenterAccessInput) (*types.DeleteTrustCenterAccessPayload, error) {
|
||||||
prb := r.ProboService(ctx, input.ID.TenantID())
|
prb := r.ProboService(ctx, input.ID.TenantID())
|
||||||
|
|
||||||
err := prb.TrustCenterAccesses.Delete(ctx, &probo.DeleteTrustCenterAccessRequest{
|
err := prb.TrustCenterAccesses.Delete(ctx, input.ID)
|
||||||
ID: input.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("cannot delete trust center access: %w", err))
|
panic(fmt.Errorf("cannot delete trust center access: %w", err))
|
||||||
}
|
}
|
||||||
@@ -1515,9 +1511,7 @@ func (r *mutationResolver) UpdateTrustCenterReference(ctx context.Context, input
|
|||||||
func (r *mutationResolver) DeleteTrustCenterReference(ctx context.Context, input types.DeleteTrustCenterReferenceInput) (*types.DeleteTrustCenterReferencePayload, error) {
|
func (r *mutationResolver) DeleteTrustCenterReference(ctx context.Context, input types.DeleteTrustCenterReferenceInput) (*types.DeleteTrustCenterReferencePayload, error) {
|
||||||
prb := r.ProboService(ctx, input.ID.TenantID())
|
prb := r.ProboService(ctx, input.ID.TenantID())
|
||||||
|
|
||||||
err := prb.TrustCenterReferences.Delete(ctx, &probo.DeleteTrustCenterReferenceRequest{
|
err := prb.TrustCenterReferences.Delete(ctx, input.ID)
|
||||||
ID: input.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("cannot delete trust center reference: %w", err))
|
panic(fmt.Errorf("cannot delete trust center reference: %w", err))
|
||||||
}
|
}
|
||||||
@@ -1589,9 +1583,7 @@ func (r *mutationResolver) GetTrustCenterFile(ctx context.Context, input types.G
|
|||||||
func (r *mutationResolver) DeleteTrustCenterFile(ctx context.Context, input types.DeleteTrustCenterFileInput) (*types.DeleteTrustCenterFilePayload, error) {
|
func (r *mutationResolver) DeleteTrustCenterFile(ctx context.Context, input types.DeleteTrustCenterFileInput) (*types.DeleteTrustCenterFilePayload, error) {
|
||||||
prb := r.ProboService(ctx, input.ID.TenantID())
|
prb := r.ProboService(ctx, input.ID.TenantID())
|
||||||
|
|
||||||
err := prb.TrustCenterFiles.Delete(ctx, &probo.DeleteTrustCenterFileRequest{
|
err := prb.TrustCenterFiles.Delete(ctx, input.ID)
|
||||||
ID: input.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("cannot delete trust center file: %w", err))
|
panic(fmt.Errorf("cannot delete trust center file: %w", err))
|
||||||
}
|
}
|
||||||
@@ -2858,7 +2850,7 @@ func (r *mutationResolver) PublishDocumentVersion(ctx context.Context, input typ
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
var errNoChanges *coredata.ErrDocumentVersionNoChanges
|
var errNoChanges *coredata.ErrDocumentVersionNoChanges
|
||||||
if errors.As(err, &errNoChanges) {
|
if errors.As(err, &errNoChanges) {
|
||||||
return nil, gqlutils.Invalid(errNoChanges)
|
return nil, gqlutils.Invalid(errNoChanges, nil)
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("cannot publish document version: %w", err))
|
panic(fmt.Errorf("cannot publish document version: %w", err))
|
||||||
}
|
}
|
||||||
@@ -3568,7 +3560,7 @@ func (r *mutationResolver) CreateProcessingActivity(ctx context.Context, input t
|
|||||||
Recipients: input.Recipients,
|
Recipients: input.Recipients,
|
||||||
Location: input.Location,
|
Location: input.Location,
|
||||||
InternationalTransfers: input.InternationalTransfers,
|
InternationalTransfers: input.InternationalTransfers,
|
||||||
TransferSafeguards: input.TransferSafeguards,
|
TransferSafeguard: input.TransferSafeguard,
|
||||||
RetentionPeriod: input.RetentionPeriod,
|
RetentionPeriod: input.RetentionPeriod,
|
||||||
SecurityMeasures: input.SecurityMeasures,
|
SecurityMeasures: input.SecurityMeasures,
|
||||||
DataProtectionImpactAssessment: input.DataProtectionImpactAssessment,
|
DataProtectionImpactAssessment: input.DataProtectionImpactAssessment,
|
||||||
@@ -3601,7 +3593,7 @@ func (r *mutationResolver) UpdateProcessingActivity(ctx context.Context, input t
|
|||||||
Recipients: UnwrapOmittable(input.Recipients),
|
Recipients: UnwrapOmittable(input.Recipients),
|
||||||
Location: UnwrapOmittable(input.Location),
|
Location: UnwrapOmittable(input.Location),
|
||||||
InternationalTransfers: input.InternationalTransfers,
|
InternationalTransfers: input.InternationalTransfers,
|
||||||
TransferSafeguards: UnwrapOmittable(input.TransferSafeguards),
|
TransferSafeguard: UnwrapOmittable(input.TransferSafeguards),
|
||||||
RetentionPeriod: UnwrapOmittable(input.RetentionPeriod),
|
RetentionPeriod: UnwrapOmittable(input.RetentionPeriod),
|
||||||
SecurityMeasures: UnwrapOmittable(input.SecurityMeasures),
|
SecurityMeasures: UnwrapOmittable(input.SecurityMeasures),
|
||||||
DataProtectionImpactAssessment: input.DataProtectionImpactAssessment,
|
DataProtectionImpactAssessment: input.DataProtectionImpactAssessment,
|
||||||
|
|||||||
@@ -30,9 +30,7 @@ func Unauthorized() *gqlerror.Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AuthenticationRequired(details map[string]any) *gqlerror.Error {
|
func AuthenticationRequired(details map[string]any) *gqlerror.Error {
|
||||||
extensions := map[string]any{
|
extensions := map[string]any{"code": "AUTHENTICATION_REQUIRED"}
|
||||||
"code": "AUTHENTICATION_REQUIRED",
|
|
||||||
}
|
|
||||||
maps.Copy(extensions, details)
|
maps.Copy(extensions, details)
|
||||||
|
|
||||||
return &gqlerror.Error{
|
return &gqlerror.Error{
|
||||||
@@ -59,11 +57,14 @@ func Conflict(err error) *gqlerror.Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Invalid(err error) *gqlerror.Error {
|
func Invalid(err error, details map[string]any) *gqlerror.Error {
|
||||||
|
extensions := map[string]any{"code": "INVALID_REQUEST"}
|
||||||
|
if details != nil {
|
||||||
|
maps.Copy(extensions, details)
|
||||||
|
}
|
||||||
|
|
||||||
return &gqlerror.Error{
|
return &gqlerror.Error{
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
Extensions: map[string]any{
|
Extensions: extensions,
|
||||||
"code": "INVALID",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
"go.gearno.de/kit/log"
|
"go.gearno.de/kit/log"
|
||||||
"go.probo.inc/probo/pkg/auth"
|
"go.probo.inc/probo/pkg/auth"
|
||||||
"go.probo.inc/probo/pkg/authz"
|
"go.probo.inc/probo/pkg/authz"
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RecoverFunc(ctx context.Context, err any) error {
|
func RecoverFunc(ctx context.Context, err any) error {
|
||||||
@@ -50,6 +51,27 @@ func RecoverFunc(ctx context.Context, err any) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errValidations validator.ValidationErrors
|
||||||
|
if errors.As(asError(err), &errValidations) {
|
||||||
|
gqlErrors := gqlerror.List{}
|
||||||
|
|
||||||
|
for _, err := range errValidations {
|
||||||
|
gqlErrors = append(
|
||||||
|
gqlErrors,
|
||||||
|
Invalid(
|
||||||
|
err,
|
||||||
|
map[string]any{
|
||||||
|
"cause": err.Code,
|
||||||
|
"field": err.Field,
|
||||||
|
"value": err.Value,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gqlErrors
|
||||||
|
}
|
||||||
|
|
||||||
var tenantAccessErr *authz.TenantAccessError
|
var tenantAccessErr *authz.TenantAccessError
|
||||||
if errTyped, ok := err.(error); ok && errors.As(errTyped, &tenantAccessErr) {
|
if errTyped, ok := err.(error); ok && errors.As(errTyped, &tenantAccessErr) {
|
||||||
return Unauthorized()
|
return Unauthorized()
|
||||||
@@ -65,5 +87,6 @@ func asError(err any) error {
|
|||||||
if e, ok := err.(error); ok {
|
if e, ok := err.(error); ok {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.New("unknown panic")
|
return errors.New("unknown panic")
|
||||||
}
|
}
|
||||||
|
|||||||
201
pkg/validator/checkeach_slice_test.go
Normal file
201
pkg/validator/checkeach_slice_test.go
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CustomType simulates types like gid.GID
|
||||||
|
type CustomType string
|
||||||
|
|
||||||
|
func TestCheckEach_EmptyTypedSlice(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
|
||||||
|
// Simulate what happens with []gid.GID{} (empty slice of custom type)
|
||||||
|
emptySlice := []CustomType{}
|
||||||
|
|
||||||
|
v.CheckEach(emptySlice, "items", func(index int, item any) {
|
||||||
|
// This callback should never be called for an empty slice
|
||||||
|
t.Error("callback should not be called for empty slice")
|
||||||
|
})
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("unexpected error for empty slice: %v", v.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckEach_NonEmptyTypedSlice(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
|
||||||
|
// Simulate what happens with []gid.GID{"abc", "def"}
|
||||||
|
slice := []CustomType{"abc", "def"}
|
||||||
|
|
||||||
|
callCount := 0
|
||||||
|
v.CheckEach(slice, "items", func(index int, item any) {
|
||||||
|
callCount++
|
||||||
|
// Verify the item is the correct type
|
||||||
|
str, ok := item.(CustomType)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected CustomType, got %T", item)
|
||||||
|
}
|
||||||
|
if index == 0 && str != "abc" {
|
||||||
|
t.Errorf("expected 'abc', got %s", str)
|
||||||
|
}
|
||||||
|
if index == 1 && str != "def" {
|
||||||
|
t.Errorf("expected 'def', got %s", str)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if callCount != 2 {
|
||||||
|
t.Errorf("expected callback to be called 2 times, got %d", callCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("unexpected error: %v", v.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckEach_NilTypedSlice(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
|
||||||
|
// Simulate what happens with var x []gid.GID (nil slice)
|
||||||
|
var nilSlice []CustomType
|
||||||
|
|
||||||
|
v.CheckEach(nilSlice, "items", func(index int, item any) {
|
||||||
|
// This callback should never be called for a nil slice
|
||||||
|
t.Error("callback should not be called for nil slice")
|
||||||
|
})
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("unexpected error for nil slice: %v", v.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckEach_PointerToNonEmptySlice(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
|
||||||
|
// Simulate what happens with *[]gid.GID (pointer to slice)
|
||||||
|
slice := []CustomType{"abc", "def", "ghi"}
|
||||||
|
ptrToSlice := &slice
|
||||||
|
|
||||||
|
callCount := 0
|
||||||
|
v.CheckEach(ptrToSlice, "items", func(index int, item any) {
|
||||||
|
callCount++
|
||||||
|
str, ok := item.(CustomType)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected CustomType, got %T", item)
|
||||||
|
}
|
||||||
|
expectedValues := []CustomType{"abc", "def", "ghi"}
|
||||||
|
if str != expectedValues[index] {
|
||||||
|
t.Errorf("at index %d: expected %s, got %s", index, expectedValues[index], str)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if callCount != 3 {
|
||||||
|
t.Errorf("expected callback to be called 3 times, got %d", callCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("unexpected error for pointer to slice: %v", v.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckEach_PointerToEmptySlice(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
|
||||||
|
// Simulate what happens with *[]gid.GID{} (pointer to empty slice)
|
||||||
|
slice := []CustomType{}
|
||||||
|
ptrToSlice := &slice
|
||||||
|
|
||||||
|
v.CheckEach(ptrToSlice, "items", func(index int, item any) {
|
||||||
|
t.Error("callback should not be called for empty slice")
|
||||||
|
})
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("unexpected error for pointer to empty slice: %v", v.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckEach_NilPointerToSlice(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
|
||||||
|
// Simulate what happens with var x *[]gid.GID (nil pointer to slice)
|
||||||
|
var nilPtrToSlice *[]CustomType
|
||||||
|
|
||||||
|
v.CheckEach(nilPtrToSlice, "items", func(index int, item any) {
|
||||||
|
t.Error("callback should not be called for nil pointer to slice")
|
||||||
|
})
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("unexpected error for nil pointer to slice: %v", v.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckEach_DoublePointerToSlice(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
|
||||||
|
// Simulate what happens with **[]gid.GID (double pointer to slice)
|
||||||
|
slice := []CustomType{"x", "y"}
|
||||||
|
ptrToSlice := &slice
|
||||||
|
doublePtrToSlice := &ptrToSlice
|
||||||
|
|
||||||
|
callCount := 0
|
||||||
|
v.CheckEach(doublePtrToSlice, "items", func(index int, item any) {
|
||||||
|
callCount++
|
||||||
|
str, ok := item.(CustomType)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("expected CustomType, got %T", item)
|
||||||
|
}
|
||||||
|
expectedValues := []CustomType{"x", "y"}
|
||||||
|
if str != expectedValues[index] {
|
||||||
|
t.Errorf("at index %d: expected %s, got %s", index, expectedValues[index], str)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if callCount != 2 {
|
||||||
|
t.Errorf("expected callback to be called 2 times, got %d", callCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("unexpected error for double pointer to slice: %v", v.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckEach_NonSliceValue(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
|
||||||
|
// Pass a non-slice value
|
||||||
|
notASlice := "this is a string"
|
||||||
|
|
||||||
|
v.CheckEach(notASlice, "items", func(index int, item any) {
|
||||||
|
t.Error("callback should not be called for non-slice value")
|
||||||
|
})
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected error for non-slice value")
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := v.Errors()
|
||||||
|
if len(errors) != 1 {
|
||||||
|
t.Errorf("expected 1 error, got %d", len(errors))
|
||||||
|
}
|
||||||
|
if errors[0].Code != ErrorCodeInvalidFormat {
|
||||||
|
t.Errorf("expected error code %s, got %s", ErrorCodeInvalidFormat, errors[0].Code)
|
||||||
|
}
|
||||||
|
if errors[0].Message != "expected a slice" {
|
||||||
|
t.Errorf("expected message 'expected a slice', got '%s'", errors[0].Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
98
pkg/validator/double_pointer_test.go
Normal file
98
pkg/validator/double_pointer_test.go
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.probo.inc/probo/pkg/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDoublePointerValidation(t *testing.T) {
|
||||||
|
t.Run("valid double pointer string", func(t *testing.T) {
|
||||||
|
v := validator.New()
|
||||||
|
str := "hello"
|
||||||
|
ptr := &str
|
||||||
|
doublePtr := &ptr
|
||||||
|
|
||||||
|
v.Check(doublePtr, "name", validator.Required(), validator.NotEmpty(), validator.MaxLen(1000))
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("expected no errors, got: %v", v.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid double pointer string - empty", func(t *testing.T) {
|
||||||
|
v := validator.New()
|
||||||
|
str := ""
|
||||||
|
ptr := &str
|
||||||
|
doublePtr := &ptr
|
||||||
|
|
||||||
|
v.Check(doublePtr, "name", validator.Required(), validator.NotEmpty())
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected errors for empty string")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid double pointer string - too long", func(t *testing.T) {
|
||||||
|
v := validator.New()
|
||||||
|
str := "this is a very long string that exceeds the maximum length"
|
||||||
|
ptr := &str
|
||||||
|
doublePtr := &ptr
|
||||||
|
|
||||||
|
v.Check(doublePtr, "name", validator.Required(), validator.MaxLen(10))
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected errors for string exceeding max length")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional double pointer - nil outer pointer", func(t *testing.T) {
|
||||||
|
v := validator.New()
|
||||||
|
var doublePtr **string = nil
|
||||||
|
|
||||||
|
v.Check(doublePtr, "name", validator.NotEmpty(), validator.MaxLen(1000))
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("expected no errors for nil optional field, got: %v", v.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional double pointer - nil inner pointer", func(t *testing.T) {
|
||||||
|
v := validator.New()
|
||||||
|
var ptr *string = nil
|
||||||
|
doublePtr := &ptr
|
||||||
|
|
||||||
|
v.Check(doublePtr, "name", validator.NotEmpty(), validator.MaxLen(1000))
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("expected no errors for nil optional field, got: %v", v.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("optional double pointer - valid value", func(t *testing.T) {
|
||||||
|
v := validator.New()
|
||||||
|
str := "hello"
|
||||||
|
ptr := &str
|
||||||
|
doublePtr := &ptr
|
||||||
|
|
||||||
|
v.Check(doublePtr, "name", validator.NotEmpty(), validator.MaxLen(1000))
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("expected no errors, got: %v", v.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
107
pkg/validator/errors.go
Normal file
107
pkg/validator/errors.go
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorCode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrorCodeRequired ErrorCode = "REQUIRED"
|
||||||
|
ErrorCodeInvalidFormat ErrorCode = "INVALID_FORMAT"
|
||||||
|
ErrorCodeOutOfRange ErrorCode = "OUT_OF_RANGE"
|
||||||
|
ErrorCodeTooShort ErrorCode = "TOO_SHORT"
|
||||||
|
ErrorCodeTooLong ErrorCode = "TOO_LONG"
|
||||||
|
ErrorCodeInvalidEmail ErrorCode = "INVALID_EMAIL"
|
||||||
|
ErrorCodeInvalidURL ErrorCode = "INVALID_URL"
|
||||||
|
ErrorCodeInvalidEnum ErrorCode = "INVALID_ENUM"
|
||||||
|
ErrorCodeInvalidGID ErrorCode = "INVALID_GID"
|
||||||
|
ErrorCodeUnsafeContent ErrorCode = "UNSAFE_CONTENT"
|
||||||
|
ErrorCodeCustom ErrorCode = "CUSTOM"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ValidationError struct {
|
||||||
|
Field string
|
||||||
|
Code ErrorCode
|
||||||
|
Message string
|
||||||
|
Value any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ValidationError) Error() string {
|
||||||
|
return fmt.Sprintf("%s %s", e.Field, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidationErrors []*ValidationError
|
||||||
|
|
||||||
|
func (ve ValidationErrors) Error() string {
|
||||||
|
if len(ve) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var messages []string
|
||||||
|
for _, err := range ve {
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
}
|
||||||
|
return strings.Join(messages, "; ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ve ValidationErrors) HasErrors() bool {
|
||||||
|
return len(ve) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ve ValidationErrors) Fields() []string {
|
||||||
|
fields := make([]string, 0, len(ve))
|
||||||
|
for _, err := range ve {
|
||||||
|
fields = append(fields, err.Field)
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ve ValidationErrors) ByField(field string) ValidationErrors {
|
||||||
|
var errors ValidationErrors
|
||||||
|
for _, err := range ve {
|
||||||
|
if err.Field == field {
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ve ValidationErrors) ByCode(code ErrorCode) ValidationErrors {
|
||||||
|
var errors ValidationErrors
|
||||||
|
for _, err := range ve {
|
||||||
|
if err.Code == code {
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ve ValidationErrors) First() *ValidationError {
|
||||||
|
if len(ve) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ve[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func newValidationError(code ErrorCode, message string) *ValidationError {
|
||||||
|
return &ValidationError{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
82
pkg/validator/oneof_custom_type_test.go
Normal file
82
pkg/validator/oneof_custom_type_test.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AssetType simulates coredata.AssetType
|
||||||
|
type AssetType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AssetTypePhysical AssetType = "PHYSICAL"
|
||||||
|
AssetTypeVirtual AssetType = "VIRTUAL"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (at AssetType) String() string {
|
||||||
|
return string(at)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOneOf_CustomStringType(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value any
|
||||||
|
allowed []string
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid custom type - physical",
|
||||||
|
value: AssetTypePhysical,
|
||||||
|
allowed: []string{"PHYSICAL", "VIRTUAL"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid custom type - virtual",
|
||||||
|
value: AssetTypeVirtual,
|
||||||
|
allowed: []string{"PHYSICAL", "VIRTUAL"},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid custom type",
|
||||||
|
value: AssetType("INVALID"),
|
||||||
|
allowed: []string{"PHYSICAL", "VIRTUAL"},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom type not in allowed list",
|
||||||
|
value: AssetTypePhysical,
|
||||||
|
allowed: []string{"VIRTUAL"},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
v.Check(tt.value, "asset_type", OneOfSlice(tt.allowed))
|
||||||
|
|
||||||
|
if tt.expectError {
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected error but got none")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("unexpected error: %v", v.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
116
pkg/validator/optional_pointer_test.go
Normal file
116
pkg/validator/optional_pointer_test.go
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CustomStringType simulates coredata.AssetType
|
||||||
|
type CustomStringType string
|
||||||
|
|
||||||
|
func (c CustomStringType) String() string {
|
||||||
|
return string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOptional_WithGIDPointer(t *testing.T) {
|
||||||
|
tenantID := gid.NewTenantID()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value *gid.GID
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil pointer - should skip validation",
|
||||||
|
value: nil,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid GID pointer",
|
||||||
|
value: func() *gid.GID {
|
||||||
|
g := gid.New(tenantID, 100)
|
||||||
|
return &g
|
||||||
|
}(),
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
v.Check(tt.value, "owner_id", GID(100))
|
||||||
|
|
||||||
|
if tt.expectError {
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected error but got none")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("unexpected error: %v", v.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOptional_WithCustomTypePointer(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value *CustomStringType
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil pointer - should skip validation",
|
||||||
|
value: nil,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid custom type pointer",
|
||||||
|
value: func() *CustomStringType {
|
||||||
|
v := CustomStringType("VALID")
|
||||||
|
return &v
|
||||||
|
}(),
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid custom type pointer",
|
||||||
|
value: func() *CustomStringType {
|
||||||
|
v := CustomStringType("INVALID")
|
||||||
|
return &v
|
||||||
|
}(),
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
v.Check(tt.value, "asset_type", OneOf("VALID", "ANOTHER"))
|
||||||
|
|
||||||
|
if tt.expectError {
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected error but got none")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("unexpected error: %v", v.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
149
pkg/validator/validation.go
Normal file
149
pkg/validator/validation.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Validator struct {
|
||||||
|
errors ValidationErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Validator {
|
||||||
|
return &Validator{
|
||||||
|
errors: ValidationErrors{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) Check(value any, field string, validators ...ValidatorFunc) {
|
||||||
|
if len(validators) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dereference pointer values to get the actual value for validation
|
||||||
|
actualValue := value
|
||||||
|
if value != nil {
|
||||||
|
val := reflect.ValueOf(value)
|
||||||
|
// Dereference all pointer levels
|
||||||
|
for val.Kind() == reflect.Ptr && !val.IsNil() {
|
||||||
|
val = val.Elem()
|
||||||
|
actualValue = val.Interface()
|
||||||
|
}
|
||||||
|
// If we ended up with a nil pointer at any level, set actualValue to nil
|
||||||
|
if val.Kind() == reflect.Ptr && val.IsNil() {
|
||||||
|
actualValue = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, validator := range validators {
|
||||||
|
if err := validator(actualValue); err != nil {
|
||||||
|
v.errors = append(v.errors, &ValidationError{
|
||||||
|
Field: field,
|
||||||
|
Code: err.Code,
|
||||||
|
Message: err.Message,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) CheckEach(items any, field string, fn func(index int, item any)) {
|
||||||
|
if items == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if slice, ok := items.([]any); ok {
|
||||||
|
for i, item := range slice {
|
||||||
|
fn(i, item)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val := reflect.ValueOf(items)
|
||||||
|
// Dereference pointer levels to get to the actual slice
|
||||||
|
for val.Kind() == reflect.Ptr {
|
||||||
|
if val.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val = val.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if val.Kind() != reflect.Slice {
|
||||||
|
v.errors = append(v.errors, &ValidationError{
|
||||||
|
Field: field,
|
||||||
|
Code: ErrorCodeInvalidFormat,
|
||||||
|
Message: "expected a slice",
|
||||||
|
Value: items,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < val.Len(); i++ {
|
||||||
|
fn(i, val.Index(i).Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) CheckNested(field string, fn func(v *Validator)) {
|
||||||
|
nestedValidator := New()
|
||||||
|
fn(nestedValidator)
|
||||||
|
|
||||||
|
for _, err := range nestedValidator.errors {
|
||||||
|
prefixedErr := &ValidationError{
|
||||||
|
Field: fmt.Sprintf("%s.%s", field, err.Field),
|
||||||
|
Code: err.Code,
|
||||||
|
Message: err.Message,
|
||||||
|
Value: err.Value,
|
||||||
|
}
|
||||||
|
v.errors = append(v.errors, prefixedErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) HasErrors() bool {
|
||||||
|
return len(v.errors) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) Errors() ValidationErrors {
|
||||||
|
return v.errors
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) Error() error {
|
||||||
|
if len(v.errors) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.errors
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValidatorFunc func(value any) *ValidationError
|
||||||
|
|
||||||
|
// dereferenceValue recursively dereferences all pointer levels.
|
||||||
|
// Returns the final dereferenced value and a boolean indicating if any pointer in the chain was nil.
|
||||||
|
func dereferenceValue(value any) (any, bool) {
|
||||||
|
if value == nil {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
|
||||||
|
val := reflect.ValueOf(value)
|
||||||
|
// Dereference all pointer levels
|
||||||
|
for val.Kind() == reflect.Ptr {
|
||||||
|
if val.IsNil() {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
val = val.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
return val.Interface(), false
|
||||||
|
}
|
||||||
425
pkg/validator/validation_bench_test.go
Normal file
425
pkg/validator/validation_bench_test.go
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkValidate_SingleField(b *testing.B) {
|
||||||
|
email := "test@example.com"
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v := New()
|
||||||
|
v.Check(&email, "email", Required(), Email())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkValidate_MultipleFields(b *testing.B) {
|
||||||
|
email := "test@example.com"
|
||||||
|
password := "password123"
|
||||||
|
age := 25
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v := New()
|
||||||
|
v.Check(&email, "email", Required(), Email())
|
||||||
|
v.Check(&password, "password", Required(), MinLen(8))
|
||||||
|
v.Check(&age, "age", Min(18), Max(120))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkValidate_OptionalField(b *testing.B) {
|
||||||
|
var website *string
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v := New()
|
||||||
|
v.Check(website, "website", URL())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkValidate_NestedStruct(b *testing.B) {
|
||||||
|
type Address struct {
|
||||||
|
City string
|
||||||
|
ZipCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Address Address
|
||||||
|
}
|
||||||
|
|
||||||
|
user := User{
|
||||||
|
Name: "John Doe",
|
||||||
|
Address: Address{
|
||||||
|
City: "New York",
|
||||||
|
ZipCode: "10001",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v := New()
|
||||||
|
v.Check(&user.Name, "name", Required())
|
||||||
|
v.CheckNested("address", func(av *Validator) {
|
||||||
|
av.Check(&user.Address.City, "city", Required())
|
||||||
|
av.Check(&user.Address.ZipCode, "zipCode", Pattern(`^\d{5}$`, ""))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkValidate_ArrayValidation(b *testing.B) {
|
||||||
|
type Item struct {
|
||||||
|
Name string
|
||||||
|
Price int
|
||||||
|
}
|
||||||
|
|
||||||
|
items := []Item{
|
||||||
|
{Name: "Item 1", Price: 100},
|
||||||
|
{Name: "Item 2", Price: 200},
|
||||||
|
{Name: "Item 3", Price: 300},
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v := New()
|
||||||
|
for j, item := range items {
|
||||||
|
v.CheckNested("items[0]", func(iv *Validator) {
|
||||||
|
iv.Check(&item.Name, "name", Required())
|
||||||
|
iv.Check(&item.Price, "price", Min(0))
|
||||||
|
_ = j
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEmail(b *testing.B) {
|
||||||
|
email := "test@example.com"
|
||||||
|
validator := Email()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&email)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkURL(b *testing.B) {
|
||||||
|
urlStr := "https://example.com"
|
||||||
|
validator := URL()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&urlStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUUID(b *testing.B) {
|
||||||
|
uuid := "550e8400-e29b-41d4-a716-446655440000"
|
||||||
|
validator := UUID()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMinLen(b *testing.B) {
|
||||||
|
str := "hello world"
|
||||||
|
validator := MinLen(5)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMin(b *testing.B) {
|
||||||
|
num := 42
|
||||||
|
validator := Min(18)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMinFloat(b *testing.B) {
|
||||||
|
num := 99.99
|
||||||
|
validator := MinFloat(0.01)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMaxFloat(b *testing.B) {
|
||||||
|
num := 50.50
|
||||||
|
validator := MaxFloat(99.99)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRangeFloat(b *testing.B) {
|
||||||
|
num := 50.50
|
||||||
|
validator := RangeFloat(0.01, 99.99)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNotEmpty(b *testing.B) {
|
||||||
|
str := "hello world"
|
||||||
|
validator := NotEmpty()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPattern(b *testing.B) {
|
||||||
|
zipCode := "12345"
|
||||||
|
validator := Pattern(`^\d{5}$`, "")
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&zipCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkValidate_WithErrors(b *testing.B) {
|
||||||
|
email := "invalid-email"
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v := New()
|
||||||
|
v.Check(&email, "email", Required(), Email())
|
||||||
|
if !v.HasErrors() {
|
||||||
|
b.Fatal("expected validation error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkValidate_ComplexForm(b *testing.B) {
|
||||||
|
type Address struct {
|
||||||
|
Street string
|
||||||
|
City string
|
||||||
|
ZipCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Email string
|
||||||
|
Name string
|
||||||
|
Age int
|
||||||
|
Website *string
|
||||||
|
PhoneNumber *string
|
||||||
|
Price float64
|
||||||
|
Address Address
|
||||||
|
}
|
||||||
|
|
||||||
|
website := "https://example.com"
|
||||||
|
user := User{
|
||||||
|
Email: "user@example.com",
|
||||||
|
Name: "John Doe",
|
||||||
|
Age: 30,
|
||||||
|
Website: &website,
|
||||||
|
PhoneNumber: nil,
|
||||||
|
Price: 99.99,
|
||||||
|
Address: Address{
|
||||||
|
Street: "123 Main St",
|
||||||
|
City: "New York",
|
||||||
|
ZipCode: "10001",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v := New()
|
||||||
|
v.Check(&user.Email, "email", Required(), Email())
|
||||||
|
v.Check(&user.Name, "name", Required(), MinLen(2))
|
||||||
|
v.Check(&user.Age, "age", Min(18), Max(120))
|
||||||
|
v.Check(user.Website, "website", URL())
|
||||||
|
v.Check(user.PhoneNumber, "phoneNumber", MinLen(10))
|
||||||
|
v.Check(&user.Price, "price", MinFloat(0.01))
|
||||||
|
|
||||||
|
v.CheckNested("address", func(av *Validator) {
|
||||||
|
av.Check(&user.Address.Street, "street", Required())
|
||||||
|
av.Check(&user.Address.City, "city", Required())
|
||||||
|
av.Check(&user.Address.ZipCode, "zipCode", Pattern(`^\d{5}$`, ""))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMinItems(b *testing.B) {
|
||||||
|
items := []string{"a", "b", "c"}
|
||||||
|
validator := MinItems(2)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMaxItems(b *testing.B) {
|
||||||
|
items := []string{"a", "b", "c"}
|
||||||
|
validator := MaxItems(5)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUniqueItems(b *testing.B) {
|
||||||
|
items := []string{"a", "b", "c"}
|
||||||
|
validator := UniqueItems()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAlphaNumeric(b *testing.B) {
|
||||||
|
str := "abc123DEF456"
|
||||||
|
validator := AlphaNumeric()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNoSpaces(b *testing.B) {
|
||||||
|
str := "hello-world-test"
|
||||||
|
validator := NoSpaces()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSlug(b *testing.B) {
|
||||||
|
str := "hello-world-123"
|
||||||
|
validator := Slug()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAfter(b *testing.B) {
|
||||||
|
now := time.Now()
|
||||||
|
future := now.Add(24 * time.Hour)
|
||||||
|
validator := After(now)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&future)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBefore(b *testing.B) {
|
||||||
|
now := time.Now()
|
||||||
|
past := now.Add(-24 * time.Hour)
|
||||||
|
validator := Before(now)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&past)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFutureDate(b *testing.B) {
|
||||||
|
future := time.Now().Add(24 * time.Hour)
|
||||||
|
validator := FutureDate()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&future)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPastDate(b *testing.B) {
|
||||||
|
past := time.Now().Add(-24 * time.Hour)
|
||||||
|
validator := PastDate()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&past)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEqualTo(b *testing.B) {
|
||||||
|
str1 := "password"
|
||||||
|
str2 := "password"
|
||||||
|
validator := EqualTo(&str2)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&str1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNotEqualTo(b *testing.B) {
|
||||||
|
str1 := "password"
|
||||||
|
str2 := "different"
|
||||||
|
validator := NotEqualTo(&str2)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&str1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func BenchmarkDomain(b *testing.B) {
|
||||||
|
str := "api.example.com"
|
||||||
|
validator := Domain()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkHTTPUrl(b *testing.B) {
|
||||||
|
str := "http://api.example.com/v1/users"
|
||||||
|
validator := HTTPUrl()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkHTTPSUrl(b *testing.B) {
|
||||||
|
str := "https://api.example.com/v1/users"
|
||||||
|
validator := HTTPSUrl()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = validator(&str)
|
||||||
|
}
|
||||||
|
}
|
||||||
451
pkg/validator/validation_test.go
Normal file
451
pkg/validator/validation_test.go
Normal file
@@ -0,0 +1,451 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.gearno.de/x/ref"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidator_Validate(t *testing.T) {
|
||||||
|
t.Run("single field validation", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
email := "test@example.com"
|
||||||
|
v.Check(&email, "email", Required(), Email())
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("expected no errors, got: %v", v.Errors())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple field validations", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
email := ""
|
||||||
|
password := "123"
|
||||||
|
|
||||||
|
v.Check(&email, "email", Required(), Email())
|
||||||
|
v.Check(&password, "password", Required(), MinLen(8))
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected validation errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := v.Errors()
|
||||||
|
// email: 1 error (Required), password: 1 error (MinLen - Required passes because it's not empty)
|
||||||
|
if len(errors) != 2 {
|
||||||
|
t.Errorf("expected 2 errors, got %d: %v", len(errors), errors)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("collect multiple errors for same field", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
value := "abc"
|
||||||
|
|
||||||
|
v.Check(&value, "password", MinLen(8), MaxLen(5))
|
||||||
|
|
||||||
|
errors := v.Errors()
|
||||||
|
// Both MinLen and MaxLen will fail (too short and somehow conflicts, but logically MinLen will fail)
|
||||||
|
if len(errors) < 1 {
|
||||||
|
t.Errorf("expected at least 1 error, got %d", len(errors))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidator_CheckNested(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
|
||||||
|
v.CheckNested("user", func(nv *Validator) {
|
||||||
|
email := "invalid"
|
||||||
|
nv.Check(&email, "email", Email())
|
||||||
|
|
||||||
|
nv.CheckNested("address", func(av *Validator) {
|
||||||
|
city := ""
|
||||||
|
av.Check(&city, "city", Required())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected validation errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := v.Errors()
|
||||||
|
if len(errors) != 2 {
|
||||||
|
t.Errorf("expected 2 errors, got %d", len(errors))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check field paths
|
||||||
|
expectedFields := map[string]bool{
|
||||||
|
"user.email": true,
|
||||||
|
"user.address.city": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, err := range errors {
|
||||||
|
if !expectedFields[err.Field] {
|
||||||
|
t.Errorf("unexpected field path: %s", err.Field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidator_Error(t *testing.T) {
|
||||||
|
t.Run("no errors", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
if v.Error() != nil {
|
||||||
|
t.Errorf("expected nil error, got: %v", v.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with errors", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
email := ""
|
||||||
|
v.Check(&email, "email", Required())
|
||||||
|
|
||||||
|
err := v.Error()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error, got nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidationErrors_Methods(t *testing.T) {
|
||||||
|
errors := ValidationErrors{
|
||||||
|
{Field: "email", Code: ErrorCodeInvalidEmail, Message: "invalid email"},
|
||||||
|
{Field: "password", Code: ErrorCodeTooShort, Message: "too short"},
|
||||||
|
{Field: "email", Code: ErrorCodeRequired, Message: "required"},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Fields", func(t *testing.T) {
|
||||||
|
fields := errors.Fields()
|
||||||
|
if len(fields) != 3 {
|
||||||
|
t.Errorf("expected 3 fields, got %d", len(fields))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ByField", func(t *testing.T) {
|
||||||
|
emailErrors := errors.ByField("email")
|
||||||
|
if len(emailErrors) != 2 {
|
||||||
|
t.Errorf("expected 2 email errors, got %d", len(emailErrors))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ByCode", func(t *testing.T) {
|
||||||
|
requiredErrors := errors.ByCode(ErrorCodeRequired)
|
||||||
|
if len(requiredErrors) != 1 {
|
||||||
|
t.Errorf("expected 1 required error, got %d", len(requiredErrors))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("First", func(t *testing.T) {
|
||||||
|
first := errors.First()
|
||||||
|
if first == nil {
|
||||||
|
t.Error("expected first error")
|
||||||
|
}
|
||||||
|
if first.Field != "email" {
|
||||||
|
t.Errorf("expected first field to be 'email', got '%s'", first.Field)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Error", func(t *testing.T) {
|
||||||
|
errorStr := errors.Error()
|
||||||
|
if errorStr == "" {
|
||||||
|
t.Error("expected non-empty error string")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOptionalFieldExample(t *testing.T) {
|
||||||
|
type CreateUserRequest struct {
|
||||||
|
Email string
|
||||||
|
Name string
|
||||||
|
Website *string
|
||||||
|
PhoneNumber *string
|
||||||
|
Age *int
|
||||||
|
}
|
||||||
|
|
||||||
|
website := "not-a-url"
|
||||||
|
req := CreateUserRequest{
|
||||||
|
Email: "user@example.com",
|
||||||
|
Name: "John Doe",
|
||||||
|
Website: &website,
|
||||||
|
PhoneNumber: nil,
|
||||||
|
Age: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
v := New()
|
||||||
|
|
||||||
|
v.Check(&req.Email, "email", Required(), Email())
|
||||||
|
v.Check(&req.Name, "name", Required(), MinLen(2))
|
||||||
|
v.Check(req.Website, "website", URL())
|
||||||
|
v.Check(req.PhoneNumber, "phoneNumber", MinLen(10))
|
||||||
|
v.Check(req.Age, "age", Min(18), Max(120))
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Fatal("expected validation errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := v.Errors()
|
||||||
|
|
||||||
|
websiteErr := errors.ByField("website")
|
||||||
|
if len(websiteErr) != 1 {
|
||||||
|
t.Errorf("expected 1 website error, got %d", len(websiteErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
phoneErr := errors.ByField("phoneNumber")
|
||||||
|
if len(phoneErr) != 0 {
|
||||||
|
t.Errorf("expected 0 phoneNumber errors (nil should be skipped), got %d", len(phoneErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
ageErr := errors.ByField("age")
|
||||||
|
if len(ageErr) != 0 {
|
||||||
|
t.Errorf("expected 0 age errors (nil should be skipped), got %d", len(ageErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Optional field validation errors: %s", errors.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRealWorldExample(t *testing.T) {
|
||||||
|
// Simulate a user registration form
|
||||||
|
type Address struct {
|
||||||
|
City string
|
||||||
|
ZipCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Email string
|
||||||
|
Password string
|
||||||
|
Age int
|
||||||
|
Website *string
|
||||||
|
Address Address
|
||||||
|
}
|
||||||
|
|
||||||
|
user := User{
|
||||||
|
Email: "invalid-email",
|
||||||
|
Password: "123",
|
||||||
|
Age: 15,
|
||||||
|
Website: ref.Ref("not-a-url"),
|
||||||
|
Address: Address{
|
||||||
|
City: "",
|
||||||
|
ZipCode: "12345",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
v := New()
|
||||||
|
|
||||||
|
// Validate user fields
|
||||||
|
v.Check(&user.Email, "email", Required(), Email())
|
||||||
|
v.Check(&user.Password, "password", Required(), MinLen(8))
|
||||||
|
v.Check(&user.Age, "age", Min(18), Max(120))
|
||||||
|
v.Check(user.Website, "website", URL())
|
||||||
|
|
||||||
|
// Validate nested address
|
||||||
|
v.CheckNested("address", func(av *Validator) {
|
||||||
|
av.Check(&user.Address.City, "city", Required())
|
||||||
|
av.Check(&user.Address.ZipCode, "zipCode", Pattern(`^\d{5}$`, "must be 5 digits"))
|
||||||
|
})
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Fatal("expected validation errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := v.Errors()
|
||||||
|
expectedErrors := map[string]ErrorCode{
|
||||||
|
"email": ErrorCodeInvalidEmail,
|
||||||
|
"password": ErrorCodeTooShort,
|
||||||
|
"age": ErrorCodeOutOfRange,
|
||||||
|
"website": ErrorCodeInvalidURL,
|
||||||
|
"address.city": ErrorCodeRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we have the expected errors
|
||||||
|
for field, expectedCode := range expectedErrors {
|
||||||
|
found := false
|
||||||
|
for _, err := range errors {
|
||||||
|
if err.Field == field && err.Code == expectedCode {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("expected error for field '%s' with code '%s'", field, expectedCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print errors for debugging
|
||||||
|
t.Logf("Validation errors: %s", errors.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayValidation(t *testing.T) {
|
||||||
|
type Item struct {
|
||||||
|
Name string
|
||||||
|
Price int
|
||||||
|
}
|
||||||
|
|
||||||
|
items := []Item{
|
||||||
|
{Name: "", Price: -10},
|
||||||
|
{Name: "Valid", Price: 100},
|
||||||
|
{Name: "X", Price: 10},
|
||||||
|
}
|
||||||
|
|
||||||
|
v := New()
|
||||||
|
|
||||||
|
// Validate each item
|
||||||
|
for i, item := range items {
|
||||||
|
field := fmt.Sprintf("items[%d]", i)
|
||||||
|
v.CheckNested(field, func(iv *Validator) {
|
||||||
|
iv.Check(&item.Name, "name", Required(), MinLen(2))
|
||||||
|
iv.Check(&item.Price, "price", Min(0))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Fatal("expected validation errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := v.Errors()
|
||||||
|
|
||||||
|
// Check for specific field paths
|
||||||
|
expectedFields := []string{
|
||||||
|
"items[0].name",
|
||||||
|
"items[0].price",
|
||||||
|
"items[2].name",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, expectedField := range expectedFields {
|
||||||
|
found := false
|
||||||
|
for _, err := range errors {
|
||||||
|
if err.Field == expectedField {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("expected error for field '%s'", expectedField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Array validation errors: %s", errors.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDuplicateValidators(t *testing.T) {
|
||||||
|
t.Run("duplicate MinLen creates two errors", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
name := "abc"
|
||||||
|
v.Check(&name, "name", MinLen(5), MinLen(5))
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected validation errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := v.Errors()
|
||||||
|
if len(errors) != 2 {
|
||||||
|
t.Errorf("expected 2 errors (one per MinLen), got %d", len(errors))
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors[0].Message != "must be at least 5 characters" {
|
||||||
|
t.Errorf("unexpected first error: %s", errors[0].Message)
|
||||||
|
}
|
||||||
|
if errors[1].Message != "must be at least 5 characters" {
|
||||||
|
t.Errorf("unexpected second error: %s", errors[1].Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("duplicate Required creates two errors", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
name := ""
|
||||||
|
v.Check(&name, "name", Required(), Required())
|
||||||
|
|
||||||
|
errors := v.Errors()
|
||||||
|
if len(errors) != 2 {
|
||||||
|
t.Errorf("expected 2 errors, got %d", len(errors))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("duplicate Email creates two errors", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
email := "invalid"
|
||||||
|
v.Check(&email, "email", Email(), Email())
|
||||||
|
|
||||||
|
errors := v.Errors()
|
||||||
|
if len(errors) != 2 {
|
||||||
|
t.Errorf("expected 2 errors, got %d", len(errors))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("same validator with different parameters", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
name := "test"
|
||||||
|
v.Check(&name, "name", MinLen(5), MinLen(10))
|
||||||
|
|
||||||
|
errors := v.Errors()
|
||||||
|
if len(errors) != 2 {
|
||||||
|
t.Errorf("expected 2 errors, got %d", len(errors))
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors[0].Message != "must be at least 5 characters" {
|
||||||
|
t.Errorf("unexpected first error: %s", errors[0].Message)
|
||||||
|
}
|
||||||
|
if errors[1].Message != "must be at least 10 characters" {
|
||||||
|
t.Errorf("unexpected second error: %s", errors[1].Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStandardErrorPattern(t *testing.T) {
|
||||||
|
// Simulates a typical validation function
|
||||||
|
validateUser := func(email, password string) error {
|
||||||
|
v := New()
|
||||||
|
v.Check(&email, "email", Required(), Email())
|
||||||
|
v.Check(&password, "password", Required(), MinLen(8))
|
||||||
|
return v.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("valid data returns nil", func(t *testing.T) {
|
||||||
|
err := validateUser("user@example.com", "password123")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected nil, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid data returns ValidationErrors as error", func(t *testing.T) {
|
||||||
|
err := validateUser("", "123")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected validation errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard error handling
|
||||||
|
t.Logf("validation failed: %v", err)
|
||||||
|
|
||||||
|
// Can get detailed errors if needed
|
||||||
|
if validationErrs, ok := err.(ValidationErrors); ok {
|
||||||
|
for _, e := range validationErrs {
|
||||||
|
t.Logf(" - %s: %s (code: %s)", e.Field, e.Message, e.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can use helper methods
|
||||||
|
emailErrs := validationErrs.ByField("email")
|
||||||
|
if len(emailErrs) != 1 {
|
||||||
|
t.Errorf("expected 1 email error, got %d", len(emailErrs))
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordErrs := validationErrs.ByField("password")
|
||||||
|
if len(passwordErrs) != 1 {
|
||||||
|
t.Errorf("expected 1 password error, got %d", len(passwordErrs))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Error("expected ValidationErrors type")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
109
pkg/validator/validator_collection.go
Normal file
109
pkg/validator/validator_collection.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MinItems validates that a slice or array has at least the specified minimum number of items.
|
||||||
|
func MinItems(min int) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
if v.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a slice or array")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Len() < min {
|
||||||
|
return newValidationError(
|
||||||
|
ErrorCodeOutOfRange,
|
||||||
|
fmt.Sprintf("must contain at least %d items", min),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxItems validates that a slice or array does not exceed the specified maximum number of items.
|
||||||
|
func MaxItems(max int) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
if v.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a slice or array")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Len() > max {
|
||||||
|
return newValidationError(
|
||||||
|
ErrorCodeOutOfRange,
|
||||||
|
fmt.Sprintf("must contain at most %d items", max),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniqueItems validates that all items in a slice or array are unique.
|
||||||
|
func UniqueItems() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
v := reflect.ValueOf(value)
|
||||||
|
if v.Kind() == reflect.Ptr {
|
||||||
|
if v.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a slice or array")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
elemType := v.Type().Elem()
|
||||||
|
if !elemType.Comparable() {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "cannot validate uniqueness for non-comparable types")
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[any]bool)
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
item := v.Index(i).Interface()
|
||||||
|
if seen[item] {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "items must be unique")
|
||||||
|
}
|
||||||
|
seen[item] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
220
pkg/validator/validator_collection_test.go
Normal file
220
pkg/validator/validator_collection_test.go
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMinItems(t *testing.T) {
|
||||||
|
t.Run("valid slice", func(t *testing.T) {
|
||||||
|
items := []string{"a", "b", "c"}
|
||||||
|
err := MinItems(2)(&items)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("exact minimum", func(t *testing.T) {
|
||||||
|
items := []int{1, 2}
|
||||||
|
err := MinItems(2)(&items)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("too few items", func(t *testing.T) {
|
||||||
|
items := []string{"a"}
|
||||||
|
err := MinItems(2)(&items)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
if err.Code != ErrorCodeOutOfRange {
|
||||||
|
t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil slice", func(t *testing.T) {
|
||||||
|
var items *[]string
|
||||||
|
err := MinItems(2)(items)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-slice value", func(t *testing.T) {
|
||||||
|
value := "not a slice"
|
||||||
|
err := MinItems(2)(&value)
|
||||||
|
if err == nil || err.Code != ErrorCodeInvalidFormat {
|
||||||
|
t.Error("expected invalid format error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxItems(t *testing.T) {
|
||||||
|
t.Run("valid slice", func(t *testing.T) {
|
||||||
|
items := []string{"a", "b"}
|
||||||
|
err := MaxItems(5)(&items)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("exact maximum", func(t *testing.T) {
|
||||||
|
items := []int{1, 2, 3}
|
||||||
|
err := MaxItems(3)(&items)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("too many items", func(t *testing.T) {
|
||||||
|
items := []string{"a", "b", "c", "d"}
|
||||||
|
err := MaxItems(2)(&items)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
if err.Code != ErrorCodeOutOfRange {
|
||||||
|
t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil slice", func(t *testing.T) {
|
||||||
|
var items *[]string
|
||||||
|
err := MaxItems(2)(items)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUniqueItems(t *testing.T) {
|
||||||
|
t.Run("unique items", func(t *testing.T) {
|
||||||
|
items := []string{"a", "b", "c"}
|
||||||
|
err := UniqueItems()(&items)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("duplicate items", func(t *testing.T) {
|
||||||
|
items := []string{"a", "b", "a"}
|
||||||
|
err := UniqueItems()(&items)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
if err.Code != ErrorCodeInvalidFormat {
|
||||||
|
t.Errorf("expected error code %s, got %s", ErrorCodeInvalidFormat, err.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("unique integers", func(t *testing.T) {
|
||||||
|
items := []int{1, 2, 3}
|
||||||
|
err := UniqueItems()(&items)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("duplicate integers", func(t *testing.T) {
|
||||||
|
items := []int{1, 2, 1}
|
||||||
|
err := UniqueItems()(&items)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil slice", func(t *testing.T) {
|
||||||
|
var items *[]string
|
||||||
|
err := UniqueItems()(items)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty slice", func(t *testing.T) {
|
||||||
|
items := []string{}
|
||||||
|
err := UniqueItems()(&items)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for empty slice, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-comparable type - slice of slices", func(t *testing.T) {
|
||||||
|
items := [][]int{{1, 2}, {3, 4}}
|
||||||
|
err := UniqueItems()(&items)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for non-comparable type")
|
||||||
|
}
|
||||||
|
if err.Code != ErrorCodeInvalidFormat {
|
||||||
|
t.Errorf("expected error code %s, got %s", ErrorCodeInvalidFormat, err.Code)
|
||||||
|
}
|
||||||
|
if err.Message != "cannot validate uniqueness for non-comparable types" {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-comparable type - slice of maps", func(t *testing.T) {
|
||||||
|
items := []map[string]int{{"a": 1}, {"b": 2}}
|
||||||
|
err := UniqueItems()(&items)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for non-comparable type")
|
||||||
|
}
|
||||||
|
if err.Code != ErrorCodeInvalidFormat {
|
||||||
|
t.Errorf("expected error code %s, got %s", ErrorCodeInvalidFormat, err.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-comparable type - struct with slice field", func(t *testing.T) {
|
||||||
|
type NonComparable struct {
|
||||||
|
Items []int
|
||||||
|
}
|
||||||
|
items := []NonComparable{{Items: []int{1, 2}}, {Items: []int{3, 4}}}
|
||||||
|
err := UniqueItems()(&items)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for non-comparable type")
|
||||||
|
}
|
||||||
|
if err.Code != ErrorCodeInvalidFormat {
|
||||||
|
t.Errorf("expected error code %s, got %s", ErrorCodeInvalidFormat, err.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("comparable struct with unique values", func(t *testing.T) {
|
||||||
|
type ComparableStruct struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
items := []ComparableStruct{{ID: 1, Name: "a"}, {ID: 2, Name: "b"}}
|
||||||
|
err := UniqueItems()(&items)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for comparable structs, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("comparable struct with duplicate values", func(t *testing.T) {
|
||||||
|
type ComparableStruct struct {
|
||||||
|
ID int
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
items := []ComparableStruct{{ID: 1, Name: "a"}, {ID: 2, Name: "b"}, {ID: 1, Name: "a"}}
|
||||||
|
err := UniqueItems()(&items)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for duplicate comparable structs")
|
||||||
|
}
|
||||||
|
if err.Code != ErrorCodeInvalidFormat {
|
||||||
|
t.Errorf("expected error code %s, got %s", ErrorCodeInvalidFormat, err.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
71
pkg/validator/validator_common.go
Normal file
71
pkg/validator/validator_common.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Required validates that a field has a value.
|
||||||
|
// For strings, it also checks that the value is not empty or just whitespace.
|
||||||
|
// For slices, it checks that the slice is not empty.
|
||||||
|
func Required() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return newValidationError(ErrorCodeRequired, "field is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := actualValue.(type) {
|
||||||
|
case string:
|
||||||
|
if strings.TrimSpace(v) == "" {
|
||||||
|
return newValidationError(ErrorCodeRequired, "field is required")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
rv := reflect.ValueOf(actualValue)
|
||||||
|
if rv.Kind() == reflect.Slice && rv.Len() == 0 {
|
||||||
|
return newValidationError(ErrorCodeRequired, "field is required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotEmpty validates that a field is not empty.
|
||||||
|
// Similar to Required, but can be used independently.
|
||||||
|
func NotEmpty() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := actualValue.(type) {
|
||||||
|
case string:
|
||||||
|
if strings.TrimSpace(v) == "" {
|
||||||
|
return newValidationError(ErrorCodeRequired, "field cannot be empty")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
rv := reflect.ValueOf(actualValue)
|
||||||
|
if rv.Kind() == reflect.Slice && rv.Len() == 0 {
|
||||||
|
return newValidationError(ErrorCodeRequired, "field cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
270
pkg/validator/validator_common_test.go
Normal file
270
pkg/validator/validator_common_test.go
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOptionalByDefault(t *testing.T) {
|
||||||
|
t.Run("nil value skips validation by default", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
v.Check(nil, "field", MinLen(5))
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("expected no errors for nil (optional by default), got: %v", v.Errors())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil pointer skips validation by default", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
var str *string
|
||||||
|
v.Check(str, "field", MinLen(5))
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("expected no errors for nil pointer (optional by default), got: %v", v.Errors())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid value passes validation", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
str := "hello world"
|
||||||
|
v.Check(&str, "field", MinLen(5))
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("expected no errors, got: %v", v.Errors())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid value fails validation", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
str := "hi"
|
||||||
|
v.Check(&str, "field", MinLen(5))
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple validators", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
str := "hello"
|
||||||
|
v.Check(&str, "field", MinLen(3), MaxLen(10))
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("expected no errors, got: %v", v.Errors())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty string is not nil and gets validated", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
str := ""
|
||||||
|
v.Check(&str, "field", MinLen(5))
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected validation error for empty string")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Required() validates nil values", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
var str *string
|
||||||
|
v.Check(str, "field", Required())
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected validation error for nil with Required()")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequired(t *testing.T) {
|
||||||
|
t.Run("valid string", func(t *testing.T) {
|
||||||
|
str := "hello"
|
||||||
|
err := Required()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty string", func(t *testing.T) {
|
||||||
|
str := ""
|
||||||
|
err := Required()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
if err.Code != ErrorCodeRequired {
|
||||||
|
t.Errorf("expected error code %s, got %s", ErrorCodeRequired, err.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("whitespace string", func(t *testing.T) {
|
||||||
|
str := " "
|
||||||
|
err := Required()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for whitespace")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil string pointer", func(t *testing.T) {
|
||||||
|
var str *string
|
||||||
|
err := Required()(str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for nil pointer")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid string pointer", func(t *testing.T) {
|
||||||
|
str := "hello"
|
||||||
|
err := Required()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil interface", func(t *testing.T) {
|
||||||
|
err := Required()(nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for nil")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("zero int", func(t *testing.T) {
|
||||||
|
num := 0
|
||||||
|
err := Required()(&num)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for zero int, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("positive int", func(t *testing.T) {
|
||||||
|
num := 42
|
||||||
|
err := Required()(&num)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil int pointer", func(t *testing.T) {
|
||||||
|
var num *int
|
||||||
|
err := Required()(num)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for nil int pointer")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid int pointer", func(t *testing.T) {
|
||||||
|
num := 42
|
||||||
|
err := Required()(&num)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty slice", func(t *testing.T) {
|
||||||
|
slice := []any{}
|
||||||
|
err := Required()(slice)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for empty slice")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-empty slice", func(t *testing.T) {
|
||||||
|
slice := []any{1, 2, 3}
|
||||||
|
err := Required()(slice)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty string slice", func(t *testing.T) {
|
||||||
|
slice := []string{}
|
||||||
|
err := Required()(slice)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for empty []string slice")
|
||||||
|
}
|
||||||
|
if err.Code != ErrorCodeRequired {
|
||||||
|
t.Errorf("expected error code %s, got %s", ErrorCodeRequired, err.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-empty string slice", func(t *testing.T) {
|
||||||
|
slice := []string{"a", "b", "c"}
|
||||||
|
err := Required()(slice)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for non-empty []string, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty int slice", func(t *testing.T) {
|
||||||
|
slice := []int{}
|
||||||
|
err := Required()(slice)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for empty []int slice")
|
||||||
|
}
|
||||||
|
if err.Code != ErrorCodeRequired {
|
||||||
|
t.Errorf("expected error code %s, got %s", ErrorCodeRequired, err.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-empty int slice", func(t *testing.T) {
|
||||||
|
slice := []int{1, 2, 3}
|
||||||
|
err := Required()(slice)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for non-empty []int, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty custom type slice", func(t *testing.T) {
|
||||||
|
type CustomType struct {
|
||||||
|
ID int
|
||||||
|
}
|
||||||
|
slice := []CustomType{}
|
||||||
|
err := Required()(slice)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for empty custom type slice")
|
||||||
|
}
|
||||||
|
if err.Code != ErrorCodeRequired {
|
||||||
|
t.Errorf("expected error code %s, got %s", ErrorCodeRequired, err.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-empty custom type slice", func(t *testing.T) {
|
||||||
|
type CustomType struct {
|
||||||
|
ID int
|
||||||
|
}
|
||||||
|
slice := []CustomType{{ID: 1}, {ID: 2}}
|
||||||
|
err := Required()(slice)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for non-empty custom type slice, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty pointer slice", func(t *testing.T) {
|
||||||
|
slice := []*string{}
|
||||||
|
err := Required()(slice)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for empty []*string slice")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-empty pointer slice", func(t *testing.T) {
|
||||||
|
str1, str2 := "a", "b"
|
||||||
|
slice := []*string{&str1, &str2}
|
||||||
|
err := Required()(slice)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for non-empty []*string, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
71
pkg/validator/validator_conditional.go
Normal file
71
pkg/validator/validator_conditional.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EqualTo validates that a value equals another value using deep equality.
|
||||||
|
// Special handling for time.Time to compare instants rather than internal structure.
|
||||||
|
func EqualTo(other any) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
if !areEqual(value, other) {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "values must match")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotEqualTo validates that a value does not equal another value using deep equality.
|
||||||
|
// Special handling for time.Time to compare instants rather than internal structure.
|
||||||
|
func NotEqualTo(other any) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
if areEqual(value, other) {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "values must not match")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// areEqual compares two values for equality with special handling for time.Time.
|
||||||
|
func areEqual(a, b any) bool {
|
||||||
|
// Dereference both values
|
||||||
|
aVal, aIsNil := dereferenceValue(a)
|
||||||
|
bVal, bIsNil := dereferenceValue(b)
|
||||||
|
|
||||||
|
// If both are nil, they're equal
|
||||||
|
if aIsNil && bIsNil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If only one is nil, they're not equal
|
||||||
|
if aIsNil || bIsNil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special handling for time.Time
|
||||||
|
aTime, aIsTime := aVal.(time.Time)
|
||||||
|
bTime, bIsTime := bVal.(time.Time)
|
||||||
|
|
||||||
|
if aIsTime && bIsTime {
|
||||||
|
// Use time.Time.Equal() which compares the instant, ignoring location and monotonic clock
|
||||||
|
return aTime.Equal(bTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to reflect.DeepEqual for all other types
|
||||||
|
return reflect.DeepEqual(aVal, bVal)
|
||||||
|
}
|
||||||
182
pkg/validator/validator_conditional_test.go
Normal file
182
pkg/validator/validator_conditional_test.go
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEqualTo(t *testing.T) {
|
||||||
|
t.Run("equal strings", func(t *testing.T) {
|
||||||
|
str1 := "password"
|
||||||
|
str2 := "password"
|
||||||
|
err := EqualTo(&str2)(&str1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("different strings", func(t *testing.T) {
|
||||||
|
str1 := "password"
|
||||||
|
str2 := "different"
|
||||||
|
err := EqualTo(&str2)(&str1)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("equal integers", func(t *testing.T) {
|
||||||
|
num1 := 42
|
||||||
|
num2 := 42
|
||||||
|
err := EqualTo(&num2)(&num1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("different integers", func(t *testing.T) {
|
||||||
|
num1 := 42
|
||||||
|
num2 := 43
|
||||||
|
err := EqualTo(&num2)(&num1)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotEqualTo(t *testing.T) {
|
||||||
|
t.Run("different strings", func(t *testing.T) {
|
||||||
|
str1 := "password"
|
||||||
|
str2 := "different"
|
||||||
|
err := NotEqualTo(&str2)(&str1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("equal strings", func(t *testing.T) {
|
||||||
|
str1 := "password"
|
||||||
|
str2 := "password"
|
||||||
|
err := NotEqualTo(&str2)(&str1)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEqualTo_TimeComparison(t *testing.T) {
|
||||||
|
t.Run("same instant same location", func(t *testing.T) {
|
||||||
|
time1 := time.Date(2025, 11, 5, 12, 0, 0, 0, time.UTC)
|
||||||
|
time2 := time.Date(2025, 11, 5, 12, 0, 0, 0, time.UTC)
|
||||||
|
err := EqualTo(time2)(time1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for same instant, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("same instant different location", func(t *testing.T) {
|
||||||
|
// Create the same instant in different time zones
|
||||||
|
utcTime := time.Date(2025, 11, 5, 12, 0, 0, 0, time.UTC)
|
||||||
|
est, _ := time.LoadLocation("America/New_York")
|
||||||
|
estTime := time.Date(2025, 11, 5, 7, 0, 0, 0, est) // 7am EST = 12pm UTC
|
||||||
|
|
||||||
|
err := EqualTo(utcTime)(estTime)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for same instant in different locations, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("different instants same location", func(t *testing.T) {
|
||||||
|
time1 := time.Date(2025, 11, 5, 12, 0, 0, 0, time.UTC)
|
||||||
|
time2 := time.Date(2025, 11, 5, 13, 0, 0, 0, time.UTC)
|
||||||
|
err := EqualTo(time2)(time1)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for different instants")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("pointer to time same instant", func(t *testing.T) {
|
||||||
|
time1 := time.Date(2025, 11, 5, 12, 0, 0, 0, time.UTC)
|
||||||
|
time2 := time.Date(2025, 11, 5, 12, 0, 0, 0, time.UTC)
|
||||||
|
err := EqualTo(&time2)(&time1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for pointer to same instant, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil time pointers", func(t *testing.T) {
|
||||||
|
var time1 *time.Time
|
||||||
|
var time2 *time.Time
|
||||||
|
err := EqualTo(time2)(time1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil time pointers, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("one nil one non-nil time pointer", func(t *testing.T) {
|
||||||
|
var time1 *time.Time
|
||||||
|
time2 := time.Date(2025, 11, 5, 12, 0, 0, 0, time.UTC)
|
||||||
|
err := EqualTo(&time2)(time1)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for nil vs non-nil time")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("same instant with monotonic clock difference", func(t *testing.T) {
|
||||||
|
// Simulate times with different monotonic clock data
|
||||||
|
baseTime := time.Date(2025, 11, 5, 12, 0, 0, 0, time.UTC)
|
||||||
|
time1 := baseTime
|
||||||
|
time.Sleep(1 * time.Millisecond) // Advances monotonic clock
|
||||||
|
time2 := baseTime
|
||||||
|
|
||||||
|
// Even though monotonic clocks differ, the instants are the same
|
||||||
|
err := EqualTo(time2)(time1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error despite monotonic clock difference, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNotEqualTo_TimeComparison(t *testing.T) {
|
||||||
|
t.Run("different instants", func(t *testing.T) {
|
||||||
|
time1 := time.Date(2025, 11, 5, 12, 0, 0, 0, time.UTC)
|
||||||
|
time2 := time.Date(2025, 11, 5, 13, 0, 0, 0, time.UTC)
|
||||||
|
err := NotEqualTo(time2)(time1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for different instants, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("same instant same location", func(t *testing.T) {
|
||||||
|
time1 := time.Date(2025, 11, 5, 12, 0, 0, 0, time.UTC)
|
||||||
|
time2 := time.Date(2025, 11, 5, 12, 0, 0, 0, time.UTC)
|
||||||
|
err := NotEqualTo(time2)(time1)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for same instant")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("same instant different location", func(t *testing.T) {
|
||||||
|
utcTime := time.Date(2025, 11, 5, 12, 0, 0, 0, time.UTC)
|
||||||
|
est, _ := time.LoadLocation("America/New_York")
|
||||||
|
estTime := time.Date(2025, 11, 5, 7, 0, 0, 0, est)
|
||||||
|
|
||||||
|
err := NotEqualTo(utcTime)(estTime)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for same instant in different locations")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
26
pkg/validator/validator_custom.go
Normal file
26
pkg/validator/validator_custom.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
// Custom creates a custom validator with a specified error code, message, and validation function.
|
||||||
|
// The validation function should return true if the value is valid, false otherwise.
|
||||||
|
func Custom(code ErrorCode, message string, fn func(value any) bool) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
if !fn(value) {
|
||||||
|
return newValidationError(code, message)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
47
pkg/validator/validator_custom_test.go
Normal file
47
pkg/validator/validator_custom_test.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCustom(t *testing.T) {
|
||||||
|
validator := Custom(ErrorCodeCustom, "value must be positive", func(value any) bool {
|
||||||
|
if num, ok := value.(int); ok {
|
||||||
|
return num > 0
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value any
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{"valid positive", 5, false},
|
||||||
|
{"invalid zero", 0, true},
|
||||||
|
{"invalid negative", -5, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := validator(tt.value)
|
||||||
|
if (err != nil) != tt.wantError {
|
||||||
|
t.Errorf("Custom() error = %v, wantError %v", err, tt.wantError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
255
pkg/validator/validator_format.go
Normal file
255
pkg/validator/validator_format.go
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
|
||||||
|
gidRegex = regexp.MustCompile(`^gid://[a-zA-Z0-9\-_]+/[a-zA-Z0-9\-_]+/[a-zA-Z0-9\-_]+$`)
|
||||||
|
uuidRegex = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
|
||||||
|
domainRegex = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Email validates that a string is a valid email address.
|
||||||
|
func Email() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidEmail, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !emailRegex.MatchString(str) {
|
||||||
|
return newValidationError(ErrorCodeInvalidEmail, "invalid email address")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL validates that a string is a valid URL with http or https scheme.
|
||||||
|
func URL() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidURL, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedURL, err := url.Parse(str)
|
||||||
|
if err != nil {
|
||||||
|
return newValidationError(ErrorCodeInvalidURL, "invalid URL format")
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
|
||||||
|
return newValidationError(ErrorCodeInvalidURL, "URL must use http or https scheme")
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedURL.Host == "" {
|
||||||
|
return newValidationError(ErrorCodeInvalidURL, "URL must have a host")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPUrl validates that a string is a valid HTTP URL (not HTTPS).
|
||||||
|
func HTTPUrl() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidURL, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedURL, err := url.Parse(str)
|
||||||
|
if err != nil {
|
||||||
|
return newValidationError(ErrorCodeInvalidURL, "invalid URL format")
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedURL.Scheme != "http" {
|
||||||
|
return newValidationError(ErrorCodeInvalidURL, "URL must use http scheme")
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedURL.Host == "" {
|
||||||
|
return newValidationError(ErrorCodeInvalidURL, "URL must have a host")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPSUrl validates that a string is a valid HTTPS URL (not HTTP).
|
||||||
|
func HTTPSUrl() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidURL, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedURL, err := url.Parse(str)
|
||||||
|
if err != nil {
|
||||||
|
return newValidationError(ErrorCodeInvalidURL, "invalid URL format")
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedURL.Scheme != "https" {
|
||||||
|
return newValidationError(ErrorCodeInvalidURL, "URL must use https scheme")
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedURL.Host == "" {
|
||||||
|
return newValidationError(ErrorCodeInvalidURL, "URL must have a host")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UUID validates that a string is a valid UUID.
|
||||||
|
func UUID() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !uuidRegex.MatchString(str) {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "invalid UUID format")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GID validates that a string is a valid GID using gid.ParseGID.
|
||||||
|
// Optionally validates the entity type if provided.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
// - GID() validates any GID format
|
||||||
|
// - GID(100) validates GID with entity type 100
|
||||||
|
// - GID(100, 200) validates GID with entity type 100 or 200
|
||||||
|
func GID(entityTypes ...uint16) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
if value == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var gidValue gid.GID
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case gid.GID:
|
||||||
|
gidValue = v
|
||||||
|
case *gid.GID:
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
gidValue = *v
|
||||||
|
default:
|
||||||
|
return newValidationError(ErrorCodeInvalidGID, "value must be a GID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(entityTypes) > 0 {
|
||||||
|
parsedEntityType := gidValue.EntityType()
|
||||||
|
valid := false
|
||||||
|
for _, expected := range entityTypes {
|
||||||
|
if parsedEntityType == expected {
|
||||||
|
valid = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
return newValidationError(ErrorCodeInvalidGID, "GID has invalid entity type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain validates that a string is a valid domain name.
|
||||||
|
func Domain() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(str) > 253 {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "domain name too long (max 253 characters)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !domainRegex.MatchString(str) {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "invalid domain name format")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
432
pkg/validator/validator_format_test.go
Normal file
432
pkg/validator/validator_format_test.go
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEmail(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value any
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{"valid email", "test@example.com", false},
|
||||||
|
{"valid email with plus", "test+tag@example.com", false},
|
||||||
|
{"invalid email no @", "testexample.com", true},
|
||||||
|
{"invalid email no domain", "test@", true},
|
||||||
|
{"invalid email no TLD", "test@example", true},
|
||||||
|
{"empty string", "", false}, // Empty is allowed, use Required() to enforce
|
||||||
|
{"nil pointer", (*string)(nil), false}, // Skip validation
|
||||||
|
{"non-string", 123, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := Email()(tt.value)
|
||||||
|
if (err != nil) != tt.wantError {
|
||||||
|
t.Errorf("Email() error = %v, wantError %v", err, tt.wantError)
|
||||||
|
}
|
||||||
|
if err != nil && err.Code != ErrorCodeInvalidEmail {
|
||||||
|
t.Errorf("Expected error code %s, got %s", ErrorCodeInvalidEmail, err.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURL(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value any
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{"valid http URL", "http://example.com", false},
|
||||||
|
{"valid https URL", "https://example.com", false},
|
||||||
|
{"valid URL with path", "https://example.com/path", false},
|
||||||
|
{"invalid scheme", "ftp://example.com", true},
|
||||||
|
{"no scheme", "example.com", true},
|
||||||
|
{"no host", "https://", true},
|
||||||
|
{"empty string", "", false}, // Empty is allowed
|
||||||
|
{"nil pointer", (*string)(nil), false},
|
||||||
|
{"non-string", 123, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := URL()(tt.value)
|
||||||
|
if (err != nil) != tt.wantError {
|
||||||
|
t.Errorf("URL() error = %v, wantError %v", err, tt.wantError)
|
||||||
|
}
|
||||||
|
if err != nil && err.Code != ErrorCodeInvalidURL {
|
||||||
|
t.Errorf("Expected error code %s, got %s", ErrorCodeInvalidURL, err.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPUrl(t *testing.T) {
|
||||||
|
t.Run("valid http URL", func(t *testing.T) {
|
||||||
|
str := "http://example.com"
|
||||||
|
err := HTTPUrl()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid http URL with path", func(t *testing.T) {
|
||||||
|
str := "http://example.com/path/to/resource"
|
||||||
|
err := HTTPUrl()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid http URL with query", func(t *testing.T) {
|
||||||
|
str := "http://example.com?foo=bar"
|
||||||
|
err := HTTPUrl()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - https scheme", func(t *testing.T) {
|
||||||
|
str := "https://example.com"
|
||||||
|
err := HTTPUrl()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for https")
|
||||||
|
}
|
||||||
|
if err.Message != "URL must use http scheme" {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - no scheme", func(t *testing.T) {
|
||||||
|
str := "example.com"
|
||||||
|
err := HTTPUrl()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for missing scheme")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - no host", func(t *testing.T) {
|
||||||
|
str := "http://"
|
||||||
|
err := HTTPUrl()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for missing host")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty string", func(t *testing.T) {
|
||||||
|
str := ""
|
||||||
|
err := HTTPUrl()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for empty string, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil pointer", func(t *testing.T) {
|
||||||
|
var str *string
|
||||||
|
err := HTTPUrl()(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPSUrl(t *testing.T) {
|
||||||
|
t.Run("valid https URL", func(t *testing.T) {
|
||||||
|
str := "https://example.com"
|
||||||
|
err := HTTPSUrl()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid https URL with path", func(t *testing.T) {
|
||||||
|
str := "https://example.com/path/to/resource"
|
||||||
|
err := HTTPSUrl()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid https URL with query", func(t *testing.T) {
|
||||||
|
str := "https://api.example.com/v1/users?page=1"
|
||||||
|
err := HTTPSUrl()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - http scheme", func(t *testing.T) {
|
||||||
|
str := "http://example.com"
|
||||||
|
err := HTTPSUrl()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for http")
|
||||||
|
}
|
||||||
|
if err.Message != "URL must use https scheme" {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - ftp scheme", func(t *testing.T) {
|
||||||
|
str := "ftp://example.com"
|
||||||
|
err := HTTPSUrl()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for ftp")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - no scheme", func(t *testing.T) {
|
||||||
|
str := "example.com"
|
||||||
|
err := HTTPSUrl()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for missing scheme")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - no host", func(t *testing.T) {
|
||||||
|
str := "https://"
|
||||||
|
err := HTTPSUrl()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for missing host")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty string", func(t *testing.T) {
|
||||||
|
str := ""
|
||||||
|
err := HTTPSUrl()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for empty string, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil pointer", func(t *testing.T) {
|
||||||
|
var str *string
|
||||||
|
err := HTTPSUrl()(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDomain(t *testing.T) {
|
||||||
|
t.Run("valid domain", func(t *testing.T) {
|
||||||
|
str := "example.com"
|
||||||
|
err := Domain()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid subdomain", func(t *testing.T) {
|
||||||
|
str := "api.example.com"
|
||||||
|
err := Domain()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid nested subdomain", func(t *testing.T) {
|
||||||
|
str := "api.v1.example.com"
|
||||||
|
err := Domain()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid domain with hyphens", func(t *testing.T) {
|
||||||
|
str := "my-api.example-site.com"
|
||||||
|
err := Domain()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("single word domain", func(t *testing.T) {
|
||||||
|
str := "localhost"
|
||||||
|
err := Domain()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - starts with hyphen", func(t *testing.T) {
|
||||||
|
str := "-example.com"
|
||||||
|
err := Domain()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for domain starting with hyphen")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - ends with hyphen", func(t *testing.T) {
|
||||||
|
str := "example-.com"
|
||||||
|
err := Domain()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for domain ending with hyphen")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - contains underscore", func(t *testing.T) {
|
||||||
|
str := "example_site.com"
|
||||||
|
err := Domain()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for underscore")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - contains spaces", func(t *testing.T) {
|
||||||
|
str := "example site.com"
|
||||||
|
err := Domain()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for spaces")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - empty label", func(t *testing.T) {
|
||||||
|
str := "example..com"
|
||||||
|
err := Domain()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for empty label")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - too long", func(t *testing.T) {
|
||||||
|
str := strings.Repeat("a", 254)
|
||||||
|
err := Domain()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for domain too long")
|
||||||
|
}
|
||||||
|
if err.Message != "domain name too long (max 253 characters)" {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty string", func(t *testing.T) {
|
||||||
|
str := ""
|
||||||
|
err := Domain()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for empty string, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil pointer", func(t *testing.T) {
|
||||||
|
var str *string
|
||||||
|
err := Domain()(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGID(t *testing.T) {
|
||||||
|
// Create a valid GID for testing
|
||||||
|
tenantID := gid.TenantID([8]byte{1, 2, 3, 4, 5, 6, 7, 8})
|
||||||
|
validGID := gid.New(tenantID, 100)
|
||||||
|
|
||||||
|
t.Run("valid GID type - no entity type validation", func(t *testing.T) {
|
||||||
|
err := GID()(validGID)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid GID type - with matching entity type", func(t *testing.T) {
|
||||||
|
err := GID(100)(validGID)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid GID type - with multiple entity types", func(t *testing.T) {
|
||||||
|
err := GID(100, 200, 300)(validGID)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - wrong entity type", func(t *testing.T) {
|
||||||
|
err := GID(200)(validGID)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for wrong entity type")
|
||||||
|
}
|
||||||
|
if err.Code != ErrorCodeInvalidGID {
|
||||||
|
t.Errorf("expected error code %s, got %s", ErrorCodeInvalidGID, err.Code)
|
||||||
|
}
|
||||||
|
if err.Message != "GID has invalid entity type" {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - wrong entity type with multiple options", func(t *testing.T) {
|
||||||
|
err := GID(200, 300)(validGID)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for wrong entity type")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid - entity type matches one of multiple options", func(t *testing.T) {
|
||||||
|
err := GID(99, 100, 101)(validGID)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil GID pointer", func(t *testing.T) {
|
||||||
|
var gidPtr *gid.GID
|
||||||
|
err := GID()(gidPtr)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil GID pointer, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid GID pointer", func(t *testing.T) {
|
||||||
|
err := GID()(&validGID)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid GID pointer with entity type validation", func(t *testing.T) {
|
||||||
|
err := GID(100)(&validGID)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non-GID type", func(t *testing.T) {
|
||||||
|
err := GID()(123)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for non-GID type")
|
||||||
|
}
|
||||||
|
if err.Message != "value must be a GID" {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("string type not supported", func(t *testing.T) {
|
||||||
|
err := GID()("some-string")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for string type")
|
||||||
|
}
|
||||||
|
if err.Message != "value must be a GID" {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
203
pkg/validator/validator_numeric.go
Normal file
203
pkg/validator/validator_numeric.go
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Min validates that a number is at least the specified minimum value.
|
||||||
|
func Min(min int) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var num int
|
||||||
|
switch v := actualValue.(type) {
|
||||||
|
case int:
|
||||||
|
num = v
|
||||||
|
case int32:
|
||||||
|
num = int(v)
|
||||||
|
case int64:
|
||||||
|
num = int(v)
|
||||||
|
default:
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if num < min {
|
||||||
|
return newValidationError(
|
||||||
|
ErrorCodeOutOfRange,
|
||||||
|
fmt.Sprintf("must be at least %d", min),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max validates that a number does not exceed the specified maximum value.
|
||||||
|
func Max(max int) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var num int
|
||||||
|
switch v := actualValue.(type) {
|
||||||
|
case int:
|
||||||
|
num = v
|
||||||
|
case int32:
|
||||||
|
num = int(v)
|
||||||
|
case int64:
|
||||||
|
num = int(v)
|
||||||
|
default:
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if num > max {
|
||||||
|
return newValidationError(
|
||||||
|
ErrorCodeOutOfRange,
|
||||||
|
fmt.Sprintf("must be at most %d", max),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range validates that a number is within the specified range (inclusive).
|
||||||
|
func Range(min, max int) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var num int
|
||||||
|
switch v := actualValue.(type) {
|
||||||
|
case int:
|
||||||
|
num = v
|
||||||
|
case int32:
|
||||||
|
num = int(v)
|
||||||
|
case int64:
|
||||||
|
num = int(v)
|
||||||
|
default:
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if num < min || num > max {
|
||||||
|
return newValidationError(
|
||||||
|
ErrorCodeOutOfRange,
|
||||||
|
fmt.Sprintf("must be between %d and %d", min, max),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinFloat validates that a floating-point number is at least the specified minimum value.
|
||||||
|
func MinFloat(min float64) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var num float64
|
||||||
|
switch v := actualValue.(type) {
|
||||||
|
case float32:
|
||||||
|
num = float64(v)
|
||||||
|
case float64:
|
||||||
|
num = v
|
||||||
|
case int:
|
||||||
|
num = float64(v)
|
||||||
|
default:
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if num < min {
|
||||||
|
return newValidationError(
|
||||||
|
ErrorCodeOutOfRange,
|
||||||
|
fmt.Sprintf("must be at least %g", min),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxFloat validates that a floating-point number does not exceed the specified maximum value.
|
||||||
|
func MaxFloat(max float64) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var num float64
|
||||||
|
switch v := actualValue.(type) {
|
||||||
|
case float32:
|
||||||
|
num = float64(v)
|
||||||
|
case float64:
|
||||||
|
num = v
|
||||||
|
case int:
|
||||||
|
num = float64(v)
|
||||||
|
default:
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if num > max {
|
||||||
|
return newValidationError(
|
||||||
|
ErrorCodeOutOfRange,
|
||||||
|
fmt.Sprintf("must be at most %g", max),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RangeFloat validates that a floating-point number is within the specified range (inclusive).
|
||||||
|
func RangeFloat(min, max float64) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var num float64
|
||||||
|
switch v := actualValue.(type) {
|
||||||
|
case float32:
|
||||||
|
num = float64(v)
|
||||||
|
case float64:
|
||||||
|
num = v
|
||||||
|
case int:
|
||||||
|
num = float64(v)
|
||||||
|
default:
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if num < min || num > max {
|
||||||
|
return newValidationError(
|
||||||
|
ErrorCodeOutOfRange,
|
||||||
|
fmt.Sprintf("must be between %g and %g", min, max),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
95
pkg/validator/validator_numeric_test.go
Normal file
95
pkg/validator/validator_numeric_test.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.gearno.de/x/ref"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMin(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value any
|
||||||
|
min int
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{"valid int", 10, 5, false},
|
||||||
|
{"exact min", 5, 5, false},
|
||||||
|
{"below min", 3, 5, true},
|
||||||
|
{"valid int pointer", ref.Ref(10), 5, false},
|
||||||
|
{"nil pointer", (*int)(nil), 5, false}, // Skip validation
|
||||||
|
{"non-numeric", "test", 5, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := Min(tt.min)(tt.value)
|
||||||
|
if (err != nil) != tt.wantError {
|
||||||
|
t.Errorf("Min() error = %v, wantError %v", err, tt.wantError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMax(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value any
|
||||||
|
max int
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{"valid int", 5, 10, false},
|
||||||
|
{"exact max", 10, 10, false},
|
||||||
|
{"above max", 15, 10, true},
|
||||||
|
{"valid int pointer", ref.Ref(5), 10, false},
|
||||||
|
{"nil pointer", (*int)(nil), 10, false}, // Skip validation
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := Max(tt.max)(tt.value)
|
||||||
|
if (err != nil) != tt.wantError {
|
||||||
|
t.Errorf("Max() error = %v, wantError %v", err, tt.wantError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRange(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value any
|
||||||
|
min int
|
||||||
|
max int
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{"in range", 5, 1, 10, false},
|
||||||
|
{"at min", 1, 1, 10, false},
|
||||||
|
{"at max", 10, 1, 10, false},
|
||||||
|
{"below range", 0, 1, 10, true},
|
||||||
|
{"above range", 11, 1, 10, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := Range(tt.min, tt.max)(tt.value)
|
||||||
|
if (err != nil) != tt.wantError {
|
||||||
|
t.Errorf("Range() error = %v, wantError %v", err, tt.wantError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
173
pkg/validator/validator_security.go
Normal file
173
pkg/validator/validator_security.go
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
htmlTagRegex = regexp.MustCompile(`<[^>]*>`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NoHTML validates that a string does not contain HTML tags or angle brackets.
|
||||||
|
// It rejects:
|
||||||
|
// - HTML tags (e.g., <script>, <b>, <div>, etc.)
|
||||||
|
// - Angle brackets (< and >) even when not part of complete tags
|
||||||
|
//
|
||||||
|
// This helps prevent XSS attacks and ensures user input doesn't contain HTML markup.
|
||||||
|
// Combine with PrintableText() for comprehensive text field validation.
|
||||||
|
func NoHTML() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for HTML tags first (more specific error message)
|
||||||
|
if htmlTagRegex.MatchString(str) {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "must not contain HTML tags")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for angle brackets (even without complete tags)
|
||||||
|
if strings.ContainsAny(str, "<>") {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "must not contain angle brackets")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintableText validates that a string contains only printable UTF-8 characters.
|
||||||
|
// It rejects:
|
||||||
|
// - Control characters (including null bytes, tabs, line breaks except space)
|
||||||
|
// - Unicode direction override characters (RLO, LRO, PDF, etc.)
|
||||||
|
// - Zero-width characters (ZWSP, ZWNJ, ZWJ, etc.)
|
||||||
|
// - Other invisible or formatting characters
|
||||||
|
// - Private use area characters
|
||||||
|
// - Replacement characters
|
||||||
|
//
|
||||||
|
// This validator does NOT check for HTML tags - use NoHTML() for that.
|
||||||
|
// This is ideal for validating titles, full names, display names, and similar text fields
|
||||||
|
// where only printable characters should be allowed.
|
||||||
|
func PrintableText() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check each rune for invisible or problematic characters
|
||||||
|
for i, r := range str {
|
||||||
|
// Allow normal space
|
||||||
|
if r == ' ' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject control characters (0x00-0x1F and 0x7F-0x9F)
|
||||||
|
if r < 0x20 || (r >= 0x7F && r < 0xA0) {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, fmt.Sprintf("contains invalid control character at position %d", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject Unicode direction override and formatting characters
|
||||||
|
// U+200E LEFT-TO-RIGHT MARK (LRM)
|
||||||
|
// U+200F RIGHT-TO-LEFT MARK (RLM)
|
||||||
|
// U+202A LEFT-TO-RIGHT EMBEDDING (LRE)
|
||||||
|
// U+202B RIGHT-TO-LEFT EMBEDDING (RLE)
|
||||||
|
// U+202C POP DIRECTIONAL FORMATTING (PDF)
|
||||||
|
// U+202D LEFT-TO-RIGHT OVERRIDE (LRO)
|
||||||
|
// U+202E RIGHT-TO-LEFT OVERRIDE (RLO)
|
||||||
|
// U+2066 LEFT-TO-RIGHT ISOLATE (LRI)
|
||||||
|
// U+2067 RIGHT-TO-LEFT ISOLATE (RLI)
|
||||||
|
// U+2068 FIRST STRONG ISOLATE (FSI)
|
||||||
|
// U+2069 POP DIRECTIONAL ISOLATE (PDI)
|
||||||
|
if r >= 0x200E && r <= 0x200F || r >= 0x202A && r <= 0x202E || r >= 0x2066 && r <= 0x2069 {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, fmt.Sprintf("contains bidirectional override character at position %d", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject zero-width characters
|
||||||
|
// U+200B ZERO WIDTH SPACE (ZWSP)
|
||||||
|
// U+200C ZERO WIDTH NON-JOINER (ZWNJ)
|
||||||
|
// U+200D ZERO WIDTH JOINER (ZWJ)
|
||||||
|
// U+FEFF ZERO WIDTH NO-BREAK SPACE (BOM)
|
||||||
|
if r == 0x200B || r == 0x200C || r == 0x200D || r == 0xFEFF {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, fmt.Sprintf("contains zero-width character at position %d", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject other format characters (Cf category)
|
||||||
|
// U+00AD SOFT HYPHEN
|
||||||
|
// U+2060 WORD JOINER
|
||||||
|
// U+180E MONGOLIAN VOWEL SEPARATOR (deprecated but still problematic)
|
||||||
|
if r == 0x00AD || r == 0x2060 || r == 0x180E {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, fmt.Sprintf("contains invisible formatting character at position %d", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject private use area characters (often used for exploits)
|
||||||
|
// U+E000-U+F8FF Private Use Area
|
||||||
|
// U+F0000-U+FFFFD Supplementary Private Use Area-A
|
||||||
|
// U+100000-U+10FFFD Supplementary Private Use Area-B
|
||||||
|
if (r >= 0xE000 && r <= 0xF8FF) || (r >= 0xF0000 && r <= 0xFFFFD) || (r >= 0x100000 && r <= 0x10FFFD) {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, fmt.Sprintf("contains private use character at position %d", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reject replacement character (often indicates encoding issues)
|
||||||
|
if r == 0xFFFD {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, fmt.Sprintf("contains replacement character at position %d", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeText validates that a string is non-empty, bounded, and contains only safe content.
|
||||||
|
// It combines NotEmpty, MaxLen, NoHTML, and PrintableText validators.
|
||||||
|
func SafeText(maxLen int) ValidatorFunc {
|
||||||
|
validators := []ValidatorFunc{
|
||||||
|
NotEmpty(),
|
||||||
|
MaxLen(maxLen),
|
||||||
|
NoHTML(),
|
||||||
|
PrintableText(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
for _, validator := range validators {
|
||||||
|
if err := validator(value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
755
pkg/validator/validator_security_test.go
Normal file
755
pkg/validator/validator_security_test.go
Normal file
@@ -0,0 +1,755 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNoHTML(t *testing.T) {
|
||||||
|
t.Run("valid text without HTML", func(t *testing.T) {
|
||||||
|
str := "This is a normal text"
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid text with special characters", func(t *testing.T) {
|
||||||
|
str := "Price: $10.99 - 20% off!"
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid UTF-8 text", func(t *testing.T) {
|
||||||
|
str := "José García 张伟"
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid text with emojis", func(t *testing.T) {
|
||||||
|
str := "Hello World 🌍"
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - script tag XSS", func(t *testing.T) {
|
||||||
|
str := "<script>alert('xss')</script>"
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for script tag")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "HTML tags") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - simple bold tag", func(t *testing.T) {
|
||||||
|
str := "Hello <b>World</b>"
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for bold tag")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "HTML tags") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - div tag", func(t *testing.T) {
|
||||||
|
str := "<div>Content</div>"
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for div tag")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - self-closing tag", func(t *testing.T) {
|
||||||
|
str := "Line break<br/>here"
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for self-closing tag")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - img tag", func(t *testing.T) {
|
||||||
|
str := `<img src="x" onerror="alert(1)">`
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for img tag")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - anchor tag", func(t *testing.T) {
|
||||||
|
str := `<a href="javascript:alert(1)">Click</a>`
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for anchor tag")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - less than symbol", func(t *testing.T) {
|
||||||
|
str := "5 < 10"
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for angle bracket")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "angle brackets") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - greater than symbol", func(t *testing.T) {
|
||||||
|
str := "10 > 5"
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for angle bracket")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - both angle brackets", func(t *testing.T) {
|
||||||
|
str := "5 < x > 10"
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for angle brackets")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - malformed tag", func(t *testing.T) {
|
||||||
|
str := "text <incomplete"
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for incomplete tag")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - encoded attempt", func(t *testing.T) {
|
||||||
|
str := "<ScRiPt>alert(1)</ScRiPt>"
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for mixed case script tag")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty string", func(t *testing.T) {
|
||||||
|
str := ""
|
||||||
|
err := NoHTML()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for empty string, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil pointer", func(t *testing.T) {
|
||||||
|
var str *string
|
||||||
|
err := NoHTML()(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not a string", func(t *testing.T) {
|
||||||
|
num := 123
|
||||||
|
err := NoHTML()(&num)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for non-string")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "must be a string") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("combined with other validators", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
title := "Product Title 2024"
|
||||||
|
v.Check(&title, "title", Required(), NoHTML(), MinLen(3), MaxLen(100))
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("expected no errors, got: %v", v.Errors())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("combined with PrintableText", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
title := "José García-O'Brien"
|
||||||
|
v.Check(&title, "title", Required(), NoHTML(), PrintableText(), MinLen(3), MaxLen(100))
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("expected no errors, got: %v", v.Errors())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("combined validators catch XSS", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
malicious := "<script>alert('xss')</script>"
|
||||||
|
v.Check(&malicious, "content", Required(), NoHTML(), PrintableText())
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected validation errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should have error from NoHTML
|
||||||
|
errors := v.Errors()
|
||||||
|
found := false
|
||||||
|
for _, err := range errors {
|
||||||
|
if strings.Contains(err.Message, "HTML tags") || strings.Contains(err.Message, "angle brackets") {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Error("expected error about HTML tags or angle brackets")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("combined validators catch invisible chars and HTML", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
malicious := "<b>test\x00text</b>"
|
||||||
|
v.Check(&malicious, "content", NoHTML(), PrintableText())
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected validation errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should have at least one error (NoHTML will catch it first)
|
||||||
|
if len(v.Errors()) < 1 {
|
||||||
|
t.Error("expected at least one validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintableText(t *testing.T) {
|
||||||
|
t.Run("valid UTF-8 text with accents", func(t *testing.T) {
|
||||||
|
str := "José García"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for valid UTF-8 name, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid text with emojis", func(t *testing.T) {
|
||||||
|
str := "Hello World 🌍"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for emojis, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid Chinese characters", func(t *testing.T) {
|
||||||
|
str := "张伟"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for Chinese characters, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid Arabic text", func(t *testing.T) {
|
||||||
|
str := "محمد"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for Arabic text, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid Cyrillic text", func(t *testing.T) {
|
||||||
|
str := "Александр"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for Cyrillic text, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid text with apostrophe and hyphen", func(t *testing.T) {
|
||||||
|
str := "O'Brien-Smith"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for apostrophe and hyphen, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid text with numbers", func(t *testing.T) {
|
||||||
|
str := "Product 2024"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for text with numbers, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid text with punctuation", func(t *testing.T) {
|
||||||
|
str := "Hello, World! How are you?"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for punctuation, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid text with angle brackets", func(t *testing.T) {
|
||||||
|
str := "5 < 10 > 3"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for angle brackets (HTML checking is separate), got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - RLO character", func(t *testing.T) {
|
||||||
|
str := "test\u202Eexe.txt"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for RLO character")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "bidirectional override") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - LRO character", func(t *testing.T) {
|
||||||
|
str := "test\u202Dtext"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for LRO character")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - zero-width space", func(t *testing.T) {
|
||||||
|
str := "test\u200Btext"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for zero-width space")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "zero-width") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - zero-width non-joiner", func(t *testing.T) {
|
||||||
|
str := "test\u200Ctext"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for zero-width non-joiner")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - zero-width joiner", func(t *testing.T) {
|
||||||
|
str := "test\u200Dtext"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for zero-width joiner")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - BOM character", func(t *testing.T) {
|
||||||
|
str := "\uFEFFtest"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for BOM character")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - null byte", func(t *testing.T) {
|
||||||
|
str := "test\x00text"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for null byte")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "control character") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - tab character", func(t *testing.T) {
|
||||||
|
str := "test\ttext"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for tab character")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - newline character", func(t *testing.T) {
|
||||||
|
str := "test\ntext"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for newline character")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - carriage return", func(t *testing.T) {
|
||||||
|
str := "test\rtext"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for carriage return")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - soft hyphen", func(t *testing.T) {
|
||||||
|
str := "test\u00ADtext"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for soft hyphen")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "invisible formatting") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - word joiner", func(t *testing.T) {
|
||||||
|
str := "test\u2060text"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for word joiner")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - private use area character", func(t *testing.T) {
|
||||||
|
str := "test\uE000text"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for private use area")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "private use") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - replacement character", func(t *testing.T) {
|
||||||
|
str := "test\uFFFDtext"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for replacement character")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "replacement character") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - DEL control character", func(t *testing.T) {
|
||||||
|
str := "test\x7Ftext"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for DEL control character")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - C1 control character", func(t *testing.T) {
|
||||||
|
str := "test\u0080text"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for C1 control character")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - LTR mark", func(t *testing.T) {
|
||||||
|
str := "test\u200Etext"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for LTR mark")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - RTL mark", func(t *testing.T) {
|
||||||
|
str := "test\u200Ftext"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for RTL mark")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid with pointer", func(t *testing.T) {
|
||||||
|
str := "Valid Name"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty string", func(t *testing.T) {
|
||||||
|
str := ""
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for empty string, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil pointer", func(t *testing.T) {
|
||||||
|
var str *string
|
||||||
|
err := PrintableText()(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not a string", func(t *testing.T) {
|
||||||
|
num := 123
|
||||||
|
err := PrintableText()(&num)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for non-string")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "must be a string") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("combined with other validators", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
title := "Product Title 2024"
|
||||||
|
v.Check(&title, "title", Required(), PrintableText(), MinLen(3), MaxLen(100))
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("expected no errors, got: %v", v.Errors())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("position reported correctly", func(t *testing.T) {
|
||||||
|
str := "abc\x00def"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "position 3") {
|
||||||
|
t.Errorf("expected position 3 in error message, got: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("UTF-8 position counting", func(t *testing.T) {
|
||||||
|
// Test that position is counted correctly with UTF-8 characters
|
||||||
|
// The range loop in Go iterates by runes, so position will be rune index
|
||||||
|
str := "abc\x00"
|
||||||
|
err := PrintableText()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
// The null byte is at rune position 3 (after 'a', 'b', 'c')
|
||||||
|
if !strings.Contains(err.Message, "position 3") {
|
||||||
|
t.Errorf("expected position 3 in error message, got: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSafeText(t *testing.T) {
|
||||||
|
t.Run("valid text", func(t *testing.T) {
|
||||||
|
str := "Product Name 2024"
|
||||||
|
err := SafeText(100)(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid UTF-8 text", func(t *testing.T) {
|
||||||
|
str := "José García"
|
||||||
|
err := SafeText(50)(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid text with emoji", func(t *testing.T) {
|
||||||
|
str := "Hello World 🌍"
|
||||||
|
err := SafeText(50)(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid text with apostrophe and hyphen", func(t *testing.T) {
|
||||||
|
str := "O'Brien-Smith"
|
||||||
|
err := SafeText(50)(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - empty string", func(t *testing.T) {
|
||||||
|
str := ""
|
||||||
|
err := SafeText(100)(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for empty string")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "empty") && !strings.Contains(err.Message, "required") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - exceeds max length", func(t *testing.T) {
|
||||||
|
str := "This is a very long string that exceeds the maximum length"
|
||||||
|
err := SafeText(10)(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for exceeding max length")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "at most") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - contains HTML tags", func(t *testing.T) {
|
||||||
|
str := "Hello <b>World</b>"
|
||||||
|
err := SafeText(100)(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for HTML tags")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "HTML tags") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - contains script tag", func(t *testing.T) {
|
||||||
|
str := "<script>alert('xss')</script>"
|
||||||
|
err := SafeText(100)(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for script tag")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - contains angle brackets", func(t *testing.T) {
|
||||||
|
str := "5 < 10"
|
||||||
|
err := SafeText(100)(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for angle brackets")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "angle brackets") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - contains null byte", func(t *testing.T) {
|
||||||
|
str := "test\x00text"
|
||||||
|
err := SafeText(100)(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for null byte")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "control character") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - contains tab character", func(t *testing.T) {
|
||||||
|
str := "test\ttext"
|
||||||
|
err := SafeText(100)(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for tab character")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - contains newline", func(t *testing.T) {
|
||||||
|
str := "test\ntext"
|
||||||
|
err := SafeText(100)(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for newline")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - contains zero-width space", func(t *testing.T) {
|
||||||
|
str := "test\u200Btext"
|
||||||
|
err := SafeText(100)(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for zero-width space")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "zero-width") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - contains RLO character", func(t *testing.T) {
|
||||||
|
str := "test\u202Eexe.txt"
|
||||||
|
err := SafeText(100)(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for RLO character")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "bidirectional override") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid - contains private use area character", func(t *testing.T) {
|
||||||
|
str := "test\uE000text"
|
||||||
|
err := SafeText(100)(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for private use area")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "private use") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil pointer", func(t *testing.T) {
|
||||||
|
var str *string
|
||||||
|
err := SafeText(100)(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil pointer, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not a string", func(t *testing.T) {
|
||||||
|
num := 123
|
||||||
|
err := SafeText(100)(&num)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for non-string")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Message, "must be a string") {
|
||||||
|
t.Errorf("unexpected error message: %s", err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("combined with validator struct", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
title := "Product Title 2024"
|
||||||
|
v.Check(&title, "title", SafeText(100))
|
||||||
|
|
||||||
|
if v.HasErrors() {
|
||||||
|
t.Errorf("expected no errors, got: %v", v.Errors())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("combined with validator struct - invalid", func(t *testing.T) {
|
||||||
|
v := New()
|
||||||
|
malicious := "<script>alert('xss')</script>"
|
||||||
|
v.Check(&malicious, "content", SafeText(100))
|
||||||
|
|
||||||
|
if !v.HasErrors() {
|
||||||
|
t.Error("expected validation errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
errors := v.Errors()
|
||||||
|
found := false
|
||||||
|
for _, err := range errors {
|
||||||
|
if strings.Contains(err.Message, "HTML tags") || strings.Contains(err.Message, "angle brackets") {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Error("expected error about HTML tags or angle brackets")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("edge case - exactly at max length", func(t *testing.T) {
|
||||||
|
str := "12345"
|
||||||
|
err := SafeText(5)(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for string at max length, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("edge case - one character over max length", func(t *testing.T) {
|
||||||
|
str := "123456"
|
||||||
|
err := SafeText(5)(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for string over max length")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
275
pkg/validator/validator_string.go
Normal file
275
pkg/validator/validator_string.go
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
alphaNumericRegex = regexp.MustCompile(`^[a-zA-Z0-9]+$`)
|
||||||
|
slugRegex = regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// MinLen validates that a string has at least the specified minimum length.
|
||||||
|
func MinLen(minLength int) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(str) < minLength {
|
||||||
|
return newValidationError(
|
||||||
|
ErrorCodeTooShort,
|
||||||
|
fmt.Sprintf("must be at least %d characters", minLength),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxLen validates that a string does not exceed the specified maximum length.
|
||||||
|
func MaxLen(maxLength int) ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(str) > maxLength {
|
||||||
|
return newValidationError(
|
||||||
|
ErrorCodeTooLong,
|
||||||
|
fmt.Sprintf("must be at most %d characters", maxLength),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern validates that a string matches the specified regular expression pattern.
|
||||||
|
func Pattern(pattern string, message string) ValidatorFunc {
|
||||||
|
regex := regexp.MustCompile(pattern)
|
||||||
|
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !regex.MatchString(str) {
|
||||||
|
if message == "" {
|
||||||
|
message = fmt.Sprintf("must match pattern: %s", pattern)
|
||||||
|
}
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlphaNumeric validates that a string contains only letters and numbers.
|
||||||
|
func AlphaNumeric() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alphaNumericRegex.MatchString(str) {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "must contain only letters and numbers")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoSpaces validates that a string does not contain any spaces.
|
||||||
|
func NoSpaces() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(str, " ") {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "must not contain spaces")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slug validates that a string is a valid URL slug (lowercase letters, numbers, and hyphens).
|
||||||
|
func Slug() ValidatorFunc {
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
str, ok := actualValue.(string)
|
||||||
|
if !ok {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "value must be a string")
|
||||||
|
}
|
||||||
|
|
||||||
|
if str == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !slugRegex.MatchString(str) {
|
||||||
|
return newValidationError(ErrorCodeInvalidFormat, "must be a valid slug (lowercase letters, numbers, and hyphens)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OneOfSlice validates that a value is one of the allowed values in the slice.
|
||||||
|
// Accepts a slice of any type. Compares by value first, then by string representation.
|
||||||
|
func OneOfSlice[T any](allowed []T) ValidatorFunc {
|
||||||
|
// Build allowed map with string keys for flexible comparison
|
||||||
|
allowedMap := make(map[string]bool)
|
||||||
|
allowedStrings := make([]string, 0, len(allowed))
|
||||||
|
|
||||||
|
for _, v := range allowed {
|
||||||
|
str := fmt.Sprint(v)
|
||||||
|
allowedMap[str] = true
|
||||||
|
allowedStrings = append(allowedStrings, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
// Handle nil values first
|
||||||
|
if value == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dereference all pointer levels
|
||||||
|
actualValue := value
|
||||||
|
val := reflect.ValueOf(value)
|
||||||
|
for val.Kind() == reflect.Ptr {
|
||||||
|
if val.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
val = val.Elem()
|
||||||
|
actualValue = val.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// First try exact match with DeepEqual
|
||||||
|
for _, allowedVal := range allowed {
|
||||||
|
if reflect.DeepEqual(actualValue, allowedVal) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then try string comparison (for custom string types)
|
||||||
|
valueStr := fmt.Sprint(actualValue)
|
||||||
|
if allowedMap[valueStr] {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return newValidationError(
|
||||||
|
ErrorCodeInvalidEnum,
|
||||||
|
fmt.Sprintf("must be one of: %s", strings.Join(allowedStrings, ", ")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OneOf validates that a value is one of the allowed values.
|
||||||
|
// Accepts strings or types that implement fmt.Stringer as variadic arguments.
|
||||||
|
func OneOf(allowed ...any) ValidatorFunc {
|
||||||
|
allowedMap := make(map[string]bool)
|
||||||
|
allowedStrings := make([]string, 0, len(allowed))
|
||||||
|
|
||||||
|
for _, v := range allowed {
|
||||||
|
var str string
|
||||||
|
switch val := v.(type) {
|
||||||
|
case string:
|
||||||
|
str = val
|
||||||
|
case fmt.Stringer:
|
||||||
|
str = val.String()
|
||||||
|
default:
|
||||||
|
str = fmt.Sprint(val)
|
||||||
|
}
|
||||||
|
allowedMap[str] = true
|
||||||
|
allowedStrings = append(allowedStrings, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(value any) *ValidationError {
|
||||||
|
actualValue, isNil := dereferenceValue(value)
|
||||||
|
if isNil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var str string
|
||||||
|
switch v := actualValue.(type) {
|
||||||
|
case string:
|
||||||
|
str = v
|
||||||
|
default:
|
||||||
|
if stringer, ok := actualValue.(fmt.Stringer); ok {
|
||||||
|
str = stringer.String()
|
||||||
|
} else {
|
||||||
|
return newValidationError(ErrorCodeInvalidEnum, "value must be a string or implement fmt.Stringer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allowedMap[str] {
|
||||||
|
return newValidationError(
|
||||||
|
ErrorCodeInvalidEnum,
|
||||||
|
fmt.Sprintf("must be one of: %s", strings.Join(allowedStrings, ", ")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
303
pkg/validator/validator_string_test.go
Normal file
303
pkg/validator/validator_string_test.go
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.gearno.de/x/ref"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMinLen(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value any
|
||||||
|
minLen int
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{"valid string", "hello", 3, false},
|
||||||
|
{"exact length", "hello", 5, false},
|
||||||
|
{"too short", "hi", 5, true},
|
||||||
|
{"nil pointer", (*string)(nil), 5, false}, // Skip validation
|
||||||
|
{"valid pointer", ref.Ref("hello"), 3, false},
|
||||||
|
{"non-string", 123, 5, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := MinLen(tt.minLen)(tt.value)
|
||||||
|
if (err != nil) != tt.wantError {
|
||||||
|
t.Errorf("MinLen() error = %v, wantError %v", err, tt.wantError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMaxLen(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value any
|
||||||
|
maxLen int
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{"valid string", "hello", 10, false},
|
||||||
|
{"exact length", "hello", 5, false},
|
||||||
|
{"too long", "hello world", 5, true},
|
||||||
|
{"nil pointer", (*string)(nil), 5, false}, // Skip validation
|
||||||
|
{"valid pointer", ref.Ref("hi"), 5, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := MaxLen(tt.maxLen)(tt.value)
|
||||||
|
if (err != nil) != tt.wantError {
|
||||||
|
t.Errorf("MaxLen() error = %v, wantError %v", err, tt.wantError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPattern(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value any
|
||||||
|
pattern string
|
||||||
|
message string
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{"valid pattern", "abc123", `^[a-z0-9]+$`, "", false},
|
||||||
|
{"invalid pattern", "ABC123", `^[a-z0-9]+$`, "", true},
|
||||||
|
{"custom message", "invalid", `^valid$`, "must be 'valid'", true},
|
||||||
|
{"nil pointer", (*string)(nil), `^test$`, "", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := Pattern(tt.pattern, tt.message)(tt.value)
|
||||||
|
if (err != nil) != tt.wantError {
|
||||||
|
t.Errorf("Pattern() error = %v, wantError %v", err, tt.wantError)
|
||||||
|
}
|
||||||
|
if err != nil && tt.message != "" && err.Message != tt.message {
|
||||||
|
t.Errorf("Expected message '%s', got '%s'", tt.message, err.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlphaNumeric(t *testing.T) {
|
||||||
|
t.Run("valid alphanumeric", func(t *testing.T) {
|
||||||
|
str := "abc123"
|
||||||
|
err := AlphaNumeric()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("only letters", func(t *testing.T) {
|
||||||
|
str := "abcDEF"
|
||||||
|
err := AlphaNumeric()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("only numbers", func(t *testing.T) {
|
||||||
|
str := "123456"
|
||||||
|
err := AlphaNumeric()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("contains spaces", func(t *testing.T) {
|
||||||
|
str := "abc 123"
|
||||||
|
err := AlphaNumeric()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("contains special characters", func(t *testing.T) {
|
||||||
|
str := "abc-123"
|
||||||
|
err := AlphaNumeric()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty string", func(t *testing.T) {
|
||||||
|
str := ""
|
||||||
|
err := AlphaNumeric()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for empty string, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil pointer", func(t *testing.T) {
|
||||||
|
var str *string
|
||||||
|
err := AlphaNumeric()(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoSpaces(t *testing.T) {
|
||||||
|
t.Run("no spaces", func(t *testing.T) {
|
||||||
|
str := "hello-world"
|
||||||
|
err := NoSpaces()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("contains spaces", func(t *testing.T) {
|
||||||
|
str := "hello world"
|
||||||
|
err := NoSpaces()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("multiple spaces", func(t *testing.T) {
|
||||||
|
str := "hello world test"
|
||||||
|
err := NoSpaces()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty string", func(t *testing.T) {
|
||||||
|
str := ""
|
||||||
|
err := NoSpaces()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for empty string, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil pointer", func(t *testing.T) {
|
||||||
|
var str *string
|
||||||
|
err := NoSpaces()(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSlug(t *testing.T) {
|
||||||
|
t.Run("valid slug", func(t *testing.T) {
|
||||||
|
str := "hello-world"
|
||||||
|
err := Slug()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("valid slug with numbers", func(t *testing.T) {
|
||||||
|
str := "hello-world-123"
|
||||||
|
err := Slug()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("single word", func(t *testing.T) {
|
||||||
|
str := "hello"
|
||||||
|
err := Slug()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("contains uppercase", func(t *testing.T) {
|
||||||
|
str := "Hello-World"
|
||||||
|
err := Slug()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for uppercase")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("contains spaces", func(t *testing.T) {
|
||||||
|
str := "hello world"
|
||||||
|
err := Slug()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for spaces")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("contains underscores", func(t *testing.T) {
|
||||||
|
str := "hello_world"
|
||||||
|
err := Slug()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for underscores")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("starts with hyphen", func(t *testing.T) {
|
||||||
|
str := "-hello"
|
||||||
|
err := Slug()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for leading hyphen")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ends with hyphen", func(t *testing.T) {
|
||||||
|
str := "hello-"
|
||||||
|
err := Slug()(&str)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected validation error for trailing hyphen")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty string", func(t *testing.T) {
|
||||||
|
str := ""
|
||||||
|
err := Slug()(&str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for empty string, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("nil pointer", func(t *testing.T) {
|
||||||
|
var str *string
|
||||||
|
err := Slug()(str)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error for nil, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOneOf(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value any
|
||||||
|
allowed []string
|
||||||
|
wantError bool
|
||||||
|
}{
|
||||||
|
{"valid value", "apple", []string{"apple", "banana", "orange"}, false},
|
||||||
|
{"invalid value", "grape", []string{"apple", "banana", "orange"}, true},
|
||||||
|
{"nil pointer", (*string)(nil), []string{"apple"}, false},
|
||||||
|
{"empty string", "", []string{"apple", ""}, false},
|
||||||
|
{"non-string", 123, []string{"apple"}, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := OneOfSlice(tt.allowed)(tt.value)
|
||||||
|
if (err != nil) != tt.wantError {
|
||||||
|
t.Errorf("OneOfSlice() error = %v, wantError %v", err, tt.wantError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user