diff --git a/apps/console/src/components/form/ProcessingActivityEnumOptions.tsx b/apps/console/src/components/form/ProcessingActivityEnumOptions.tsx index 2bf3597db..fb74dded7 100644 --- a/apps/console/src/components/form/ProcessingActivityEnumOptions.tsx +++ b/apps/console/src/components/form/ProcessingActivityEnumOptions.tsx @@ -1,7 +1,7 @@ import { useTranslate } from "@probo/i18n"; import { Option } from "@probo/ui"; import type { - ProcessingActivitySpecialOrCriminalData, + ProcessingActivitySpecialOrCriminalDatum, ProcessingActivityLawfulBasis, ProcessingActivityDataProtectionImpactAssessment, ProcessingActivityTransferImpactAssessment, @@ -11,7 +11,7 @@ export function SpecialOrCriminalDataOptions() { const { __ } = useTranslate(); const options: Array<{ - value: ProcessingActivitySpecialOrCriminalData; + value: ProcessingActivitySpecialOrCriminalDatum; label: string; }> = [ { 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 "-"; const labels = { - "CONSENT": __("Consent"), - "CONTRACTUAL_NECESSITY": __("Contractual Necessity"), - "LEGAL_OBLIGATION": __("Legal Obligation"), - "LEGITIMATE_INTEREST": __("Legitimate Interest"), - "PUBLIC_TASK": __("Public Task"), - "VITAL_INTERESTS": __("Vital Interests"), + CONSENT: __("Consent"), + CONTRACTUAL_NECESSITY: __("Contractual Necessity"), + LEGAL_OBLIGATION: __("Legal Obligation"), + LEGITIMATE_INTEREST: __("Legitimate Interest"), + PUBLIC_TASK: __("Public Task"), + VITAL_INTERESTS: __("Vital Interests"), }; return labels[value] || value; @@ -79,12 +82,18 @@ export function TransferSafeguardsOptions() { label: string; }> = [ { 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: "ADEQUACY_DECISION", label: __("Adequacy Decision") }, { value: "DEROGATIONS", label: __("Derogations") }, { value: "CODES_OF_CONDUCT", label: __("Codes of Conduct") }, - { value: "CERTIFICATION_MECHANISMS", label: __("Certification Mechanisms") }, + { + value: "CERTIFICATION_MECHANISMS", + label: __("Certification Mechanisms"), + }, ]; return ( diff --git a/apps/console/src/hooks/forms/useVendorForm.tsx b/apps/console/src/hooks/forms/useVendorForm.tsx index bc7122886..c404062e8 100644 --- a/apps/console/src/hooks/forms/useVendorForm.tsx +++ b/apps/console/src/hooks/forms/useVendorForm.tsx @@ -107,6 +107,14 @@ export function useVendorForm(vendorKey: useVendorFormFragment$key) { id: vendor.id, ...data, 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(() => { diff --git a/apps/console/src/hooks/graph/AssetGraph.ts b/apps/console/src/hooks/graph/AssetGraph.ts index ea9f56348..ea126eb7b 100644 --- a/apps/console/src/hooks/graph/AssetGraph.ts +++ b/apps/console/src/hooks/graph/AssetGraph.ts @@ -3,6 +3,7 @@ import { useMutation } from "react-relay"; import { useConfirm } from "@probo/ui"; import { useTranslate } from "@probo/i18n"; import { promisifyMutation, sprintf } from "@probo/helpers"; +import { useMutationWithToasts } from "../useMutationWithToasts"; export const assetsQuery = graphql` query AssetGraphListQuery($organizationId: ID!, $snapshotId: ID) { @@ -197,8 +198,11 @@ export const useCreateAsset = (connectionId: string) => { }; export const useUpdateAsset = () => { - const [mutate] = useMutation(updateAssetMutation); const { __ } = useTranslate(); + const [mutate] = useMutationWithToasts(updateAssetMutation, { + successMessage: __("Asset updated successfully"), + errorMessage: __("Failed to update asset"), + }); return (input: { id: string; @@ -213,7 +217,7 @@ export const useUpdateAsset = () => { return alert(__("Failed to update asset: asset ID is required")); } - return promisifyMutation(mutate)({ + return mutate({ variables: { input, }, diff --git a/apps/console/src/hooks/graph/AuditGraph.ts b/apps/console/src/hooks/graph/AuditGraph.ts index 9d8263e4e..cd10bf811 100644 --- a/apps/console/src/hooks/graph/AuditGraph.ts +++ b/apps/console/src/hooks/graph/AuditGraph.ts @@ -151,7 +151,7 @@ export const useCreateAudit = (connectionId: string) => { return (input: { organizationId: string; frameworkId: string; - name?: string; + name?: string | null; validFrom?: string; validUntil?: string; reportKey?: string; @@ -187,7 +187,7 @@ export const useUpdateAudit = () => { return (input: { id: string; - name?: string; + name?: string | null; validFrom?: string | null; validUntil?: string | null; state?: string; diff --git a/apps/console/src/hooks/graph/__generated__/ProcessingActivityGraphCreateMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/ProcessingActivityGraphCreateMutation.graphql.ts index 16ade470f..8b7df08d2 100644 --- a/apps/console/src/hooks/graph/__generated__/ProcessingActivityGraphCreateMutation.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/ProcessingActivityGraphCreateMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -11,9 +11,9 @@ import { ConcreteRequest } from 'relay-runtime'; export type ProcessingActivityDataProtectionImpactAssessment = "NEEDED" | "NOT_NEEDED"; 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 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 = { consentEvidenceLink?: string | null | undefined; dataProtectionImpactAssessment: ProcessingActivityDataProtectionImpactAssessment; @@ -28,9 +28,9 @@ export type CreateProcessingActivityInput = { recipients?: string | null | undefined; retentionPeriod?: string | null | undefined; securityMeasures?: string | null | undefined; - specialOrCriminalData: ProcessingActivitySpecialOrCriminalData; + specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum; transferImpactAssessment: ProcessingActivityTransferImpactAssessment; - transferSafeguards?: ProcessingActivityTransferSafeguards | null | undefined; + transferSafeguards?: ProcessingActivityTransferSafeguard | null | undefined; vendorIds?: ReadonlyArray | null | undefined; }; export type ProcessingActivityGraphCreateMutation$variables = { @@ -55,9 +55,9 @@ export type ProcessingActivityGraphCreateMutation$data = { readonly recipients: string | null | undefined; readonly retentionPeriod: string | null | undefined; readonly securityMeasures: string | null | undefined; - readonly specialOrCriminalData: ProcessingActivitySpecialOrCriminalData; + readonly specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum; readonly transferImpactAssessment: ProcessingActivityTransferImpactAssessment; - readonly transferSafeguards: ProcessingActivityTransferSafeguards | null | undefined; + readonly transferSafeguards: ProcessingActivityTransferSafeguard | null | undefined; readonly vendors: { readonly edges: ReadonlyArray<{ readonly node: { diff --git a/apps/console/src/hooks/graph/__generated__/ProcessingActivityGraphNodeQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/ProcessingActivityGraphNodeQuery.graphql.ts index 6be92f9ce..5d626c6a4 100644 --- a/apps/console/src/hooks/graph/__generated__/ProcessingActivityGraphNodeQuery.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/ProcessingActivityGraphNodeQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<5dea7ccaf98a1223d9e4cc1ecdfbb0bc>> + * @generated SignedSource<<736925258373b1116c286fc16d19ad7d>> * @lightSyntaxTransform * @nogrep */ @@ -11,9 +11,9 @@ import { ConcreteRequest } from 'relay-runtime'; export type ProcessingActivityDataProtectionImpactAssessment = "NEEDED" | "NOT_NEEDED"; 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 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 ProcessingActivityGraphNodeQuery$variables = { processingActivityId: string; @@ -39,9 +39,9 @@ export type ProcessingActivityGraphNodeQuery$data = { readonly retentionPeriod?: string | null | undefined; readonly securityMeasures?: string | null | undefined; readonly snapshotId?: string | null | undefined; - readonly specialOrCriminalData?: ProcessingActivitySpecialOrCriminalData; + readonly specialOrCriminalData?: ProcessingActivitySpecialOrCriminalDatum; readonly transferImpactAssessment?: ProcessingActivityTransferImpactAssessment; - readonly transferSafeguards?: ProcessingActivityTransferSafeguards | null | undefined; + readonly transferSafeguards?: ProcessingActivityTransferSafeguard | null | undefined; readonly updatedAt?: any; readonly vendors?: { readonly edges: ReadonlyArray<{ diff --git a/apps/console/src/hooks/graph/__generated__/ProcessingActivityGraphUpdateMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/ProcessingActivityGraphUpdateMutation.graphql.ts index 0996cfe79..de1b6dffc 100644 --- a/apps/console/src/hooks/graph/__generated__/ProcessingActivityGraphUpdateMutation.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/ProcessingActivityGraphUpdateMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<08099a83039838c6c79dbf1ca7c99034>> + * @generated SignedSource<<7004a9f42c1d7e16eadf8adbdb613b2a>> * @lightSyntaxTransform * @nogrep */ @@ -11,9 +11,9 @@ import { ConcreteRequest } from 'relay-runtime'; export type ProcessingActivityDataProtectionImpactAssessment = "NEEDED" | "NOT_NEEDED"; 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 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 = { consentEvidenceLink?: string | null | undefined; dataProtectionImpactAssessment?: ProcessingActivityDataProtectionImpactAssessment | null | undefined; @@ -28,9 +28,9 @@ export type UpdateProcessingActivityInput = { recipients?: string | null | undefined; retentionPeriod?: string | null | undefined; securityMeasures?: string | null | undefined; - specialOrCriminalData?: ProcessingActivitySpecialOrCriminalData | null | undefined; + specialOrCriminalData?: ProcessingActivitySpecialOrCriminalDatum | null | undefined; transferImpactAssessment?: ProcessingActivityTransferImpactAssessment | null | undefined; - transferSafeguards?: ProcessingActivityTransferSafeguards | null | undefined; + transferSafeguards?: ProcessingActivityTransferSafeguard | null | undefined; vendorIds?: ReadonlyArray | null | undefined; }; export type ProcessingActivityGraphUpdateMutation$variables = { @@ -52,9 +52,9 @@ export type ProcessingActivityGraphUpdateMutation$data = { readonly recipients: string | null | undefined; readonly retentionPeriod: string | null | undefined; readonly securityMeasures: string | null | undefined; - readonly specialOrCriminalData: ProcessingActivitySpecialOrCriminalData; + readonly specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum; readonly transferImpactAssessment: ProcessingActivityTransferImpactAssessment; - readonly transferSafeguards: ProcessingActivityTransferSafeguards | null | undefined; + readonly transferSafeguards: ProcessingActivityTransferSafeguard | null | undefined; readonly updatedAt: any; readonly vendors: { readonly edges: ReadonlyArray<{ diff --git a/apps/console/src/pages/organizations/assets/AssetDetailsPage.tsx b/apps/console/src/pages/organizations/assets/AssetDetailsPage.tsx index 5f2cef4b9..4c48fb208 100644 --- a/apps/console/src/pages/organizations/assets/AssetDetailsPage.tsx +++ b/apps/console/src/pages/organizations/assets/AssetDetailsPage.tsx @@ -80,15 +80,11 @@ export default function AssetDetailsPage(props: Props) { const updateAsset = useUpdateAsset(); const onSubmit = handleSubmit(async (formData) => { - try { - await updateAsset({ - id: assetEntry?.id, - ...formData, - }); - reset(formData); - } catch (error) { - console.error("Failed to update asset:", error); - } + await updateAsset({ + id: assetEntry?.id, + ...formData, + }); + reset(formData); }); const breadcrumbAssetsUrl = isSnapshotMode && snapshotId diff --git a/apps/console/src/pages/organizations/audits/AuditDetailsPage.tsx b/apps/console/src/pages/organizations/audits/AuditDetailsPage.tsx index dd8180905..e93c79fb7 100644 --- a/apps/console/src/pages/organizations/audits/AuditDetailsPage.tsx +++ b/apps/console/src/pages/organizations/audits/AuditDetailsPage.tsx @@ -36,7 +36,7 @@ import { getAuditStateLabel, getAuditStateVariant, auditStates, fileSize, sprint import type { AuditGraphNodeQuery } from "/hooks/graph/__generated__/AuditGraphNodeQuery.graphql"; const updateAuditSchema = z.object({ - name: z.string().optional(), + name: z.string().nullable().optional(), validFrom: z.string().optional(), validUntil: z.string().optional(), 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, { defaultValues: { - name: auditEntry.name || "", + name: auditEntry.name || null, validFrom: auditEntry.validFrom?.split('T')[0] || "", validUntil: auditEntry.validUntil?.split('T')[0] || "", state: auditEntry.state || "NOT_STARTED", @@ -82,7 +82,7 @@ export default function AuditDetailsPage(props: Props) { try { await updateAudit({ id: auditEntry.id, - name: formData.name, + name: formData.name || null, validFrom: formatDatetime(formData.validFrom) ?? null, validUntil: formatDatetime(formData.validUntil) ?? null, state: formData.state, diff --git a/apps/console/src/pages/organizations/audits/dialogs/CreateAuditDialog.tsx b/apps/console/src/pages/organizations/audits/dialogs/CreateAuditDialog.tsx index 81ba3fa6e..9c09222b7 100644 --- a/apps/console/src/pages/organizations/audits/dialogs/CreateAuditDialog.tsx +++ b/apps/console/src/pages/organizations/audits/dialogs/CreateAuditDialog.tsx @@ -79,7 +79,7 @@ export function CreateAuditDialog({ await createAudit({ organizationId, frameworkId: data.frameworkId, - name: data.name, + name: data.name || null, validFrom: formatDatetime(data.validFrom), validUntil: formatDatetime(data.validUntil), state: data.state, diff --git a/pkg/coredata/asset_type.go b/pkg/coredata/asset_type.go index 15c8e26b8..8dab8d06e 100644 --- a/pkg/coredata/asset_type.go +++ b/pkg/coredata/asset_type.go @@ -28,6 +28,13 @@ const ( AssetTypeVirtual AssetType = "VIRTUAL" ) +func AssetTypes() []AssetType { + return []AssetType{ + AssetTypePhysical, + AssetTypeVirtual, + } +} + func (at AssetType) MarshalText() ([]byte, error) { return []byte(at.String()), nil } diff --git a/pkg/coredata/audit_state.go b/pkg/coredata/audit_state.go index 6d07ff873..b516c98c4 100644 --- a/pkg/coredata/audit_state.go +++ b/pkg/coredata/audit_state.go @@ -29,6 +29,16 @@ const ( AuditStateOutdated AuditState = "OUTDATED" ) +func AuditStates() []AuditState { + return []AuditState{ + AuditStateNotStarted, + AuditStateInProgress, + AuditStateCompleted, + AuditStateRejected, + AuditStateOutdated, + } +} + func (as AuditState) String() string { return string(as) } diff --git a/pkg/coredata/business_impact.go b/pkg/coredata/business_impact.go index 3d7e9f9a9..639363287 100644 --- a/pkg/coredata/business_impact.go +++ b/pkg/coredata/business_impact.go @@ -29,6 +29,15 @@ const ( BusinessImpactCritical BusinessImpact = "CRITICAL" ) +func BusinessImpacts() []BusinessImpact { + return []BusinessImpact{ + BusinessImpactLow, + BusinessImpactMedium, + BusinessImpactHigh, + BusinessImpactCritical, + } +} + func (i BusinessImpact) String() string { return string(i) } diff --git a/pkg/coredata/connector_protocol.go b/pkg/coredata/connector_protocol.go index 3929d7e9d..65e6d939a 100644 --- a/pkg/coredata/connector_protocol.go +++ b/pkg/coredata/connector_protocol.go @@ -25,6 +25,12 @@ const ( ConnectorProtocolOAuth2 ConnectorProtocol = "OAUTH2" ) +func ConnectorProtocols() []ConnectorProtocol { + return []ConnectorProtocol{ + ConnectorProtocolOAuth2, + } +} + func (cp ConnectorProtocol) String() string { return string(cp) } diff --git a/pkg/coredata/connector_provider.go b/pkg/coredata/connector_provider.go index 1f3703d79..897b0c4f6 100644 --- a/pkg/coredata/connector_provider.go +++ b/pkg/coredata/connector_provider.go @@ -25,6 +25,12 @@ const ( ConnectorProviderSlack ConnectorProvider = "SLACK" ) +func ConnectorProviders() []ConnectorProvider { + return []ConnectorProvider{ + ConnectorProviderSlack, + } +} + func (cp ConnectorProvider) String() string { return string(cp) } diff --git a/pkg/coredata/continual_improvement_priority.go b/pkg/coredata/continual_improvement_priority.go index b4d06bb65..0e7e771a1 100644 --- a/pkg/coredata/continual_improvement_priority.go +++ b/pkg/coredata/continual_improvement_priority.go @@ -27,6 +27,14 @@ const ( ContinualImprovementPriorityHigh ContinualImprovementPriority = "HIGH" ) +func ContinualImprovementPriorities() []ContinualImprovementPriority { + return []ContinualImprovementPriority{ + ContinualImprovementPriorityLow, + ContinualImprovementPriorityMedium, + ContinualImprovementPriorityHigh, + } +} + func (cip ContinualImprovementPriority) String() string { return string(cip) } diff --git a/pkg/coredata/continual_improvement_status.go b/pkg/coredata/continual_improvement_status.go index 74e543786..ff6453bcd 100644 --- a/pkg/coredata/continual_improvement_status.go +++ b/pkg/coredata/continual_improvement_status.go @@ -27,6 +27,14 @@ const ( ContinualImprovementStatusClosed ContinualImprovementStatus = "CLOSED" ) +func ContinualImprovementStatuses() []ContinualImprovementStatus { + return []ContinualImprovementStatus{ + ContinualImprovementStatusOpen, + ContinualImprovementStatusInProgress, + ContinualImprovementStatusClosed, + } +} + func (cis ContinualImprovementStatus) String() string { return string(cis) } diff --git a/pkg/coredata/control_status.go b/pkg/coredata/control_status.go index f8b5325fd..b47784ca0 100644 --- a/pkg/coredata/control_status.go +++ b/pkg/coredata/control_status.go @@ -26,6 +26,13 @@ const ( ControlStatusExcluded ControlStatus = "EXCLUDED" ) +func ControlStatuses() []ControlStatus { + return []ControlStatus{ + ControlStatusIncluded, + ControlStatusExcluded, + } +} + func (cs ControlStatus) String() string { return string(cs) } diff --git a/pkg/coredata/data_classification.go b/pkg/coredata/data_classification.go index 06c5ebf6b..ef394a060 100644 --- a/pkg/coredata/data_classification.go +++ b/pkg/coredata/data_classification.go @@ -22,3 +22,12 @@ const ( DataClassificationConfidential DataClassification = "CONFIDENTIAL" DataClassificationSecret DataClassification = "SECRET" ) + +func DataClassifications() []DataClassification { + return []DataClassification{ + DataClassificationPublic, + DataClassificationInternal, + DataClassificationConfidential, + DataClassificationSecret, + } +} diff --git a/pkg/coredata/data_sensitivity.go b/pkg/coredata/data_sensitivity.go index 9c9fe6ce6..ca57501af 100644 --- a/pkg/coredata/data_sensitivity.go +++ b/pkg/coredata/data_sensitivity.go @@ -30,6 +30,16 @@ const ( DataSensitivityCritical DataSensitivity = "CRITICAL" ) +func DataSensitivities() []DataSensitivity { + return []DataSensitivity{ + DataSensitivityNone, + DataSensitivityLow, + DataSensitivityMedium, + DataSensitivityHigh, + DataSensitivityCritical, + } +} + func (i DataSensitivity) String() string { return string(i) } diff --git a/pkg/coredata/document_classification.go b/pkg/coredata/document_classification.go index c9292dc94..f409867ff 100644 --- a/pkg/coredata/document_classification.go +++ b/pkg/coredata/document_classification.go @@ -14,6 +14,15 @@ const ( DocumentClassificationSecret DocumentClassification = "SECRET" ) +func DocumentClassifications() []DocumentClassification { + return []DocumentClassification{ + DocumentClassificationPublic, + DocumentClassificationInternal, + DocumentClassificationConfidential, + DocumentClassificationSecret, + } +} + func (dc DocumentClassification) String() string { switch dc { case DocumentClassificationPublic: diff --git a/pkg/coredata/document_type.go b/pkg/coredata/document_type.go index cfe74a147..6974d2013 100644 --- a/pkg/coredata/document_type.go +++ b/pkg/coredata/document_type.go @@ -30,6 +30,15 @@ const ( DocumentTypeProcedure DocumentType = "PROCEDURE" ) +func DocumentTypes() []DocumentType { + return []DocumentType{ + DocumentTypeOther, + DocumentTypeISMS, + DocumentTypePolicy, + DocumentTypeProcedure, + } +} + func (dt DocumentType) MarshalText() ([]byte, error) { return []byte(dt.String()), nil } diff --git a/pkg/coredata/entity_type_reg.go b/pkg/coredata/entity_type_reg.go index 40c42a8bf..cd687bd2b 100644 --- a/pkg/coredata/entity_type_reg.go +++ b/pkg/coredata/entity_type_reg.go @@ -21,49 +21,49 @@ var ( ) const ( - OrganizationEntityType uint16 = iota - FrameworkEntityType - MeasureEntityType - TaskEntityType - EvidenceEntityType - ConnectorEntityType - VendorRiskAssessmentEntityType - VendorEntityType - PeopleEntityType - VendorComplianceReportEntityType - DocumentEntityType - UserEntityType - SessionEntityType - EmailEntityType - ControlEntityType - RiskEntityType - DocumentVersionEntityType - DocumentVersionSignatureEntityType - AssetEntityType - DatumEntityType - AuditEntityType - ReportEntityType - TrustCenterEntityType - TrustCenterAccessEntityType - VendorBusinessAssociateAgreementEntityType - FileEntityType - VendorContactEntityType - VendorDataPrivacyAgreementEntityType - NonconformityEntityType - ObligationEntityType - VendorServiceEntityType - SnapshotEntityType - ContinualImprovementEntityType - ProcessingActivityEntityType - ExportJobEntityType - TrustCenterReferenceEntityType - TrustCenterDocumentAccessEntityType - CustomDomainEntityType - InvitationEntityType - MembershipEntityType - SlackMessageEntityType - TrustCenterFileEntityType - SAMLConfigurationEntityType - UserAPIKeyEntityType - UserAPIKeyMembershipEntityType + OrganizationEntityType uint16 = 0 + FrameworkEntityType uint16 = 1 + MeasureEntityType uint16 = 2 + TaskEntityType uint16 = 3 + EvidenceEntityType uint16 = 4 + ConnectorEntityType uint16 = 5 + VendorRiskAssessmentEntityType uint16 = 6 + VendorEntityType uint16 = 7 + PeopleEntityType uint16 = 8 + VendorComplianceReportEntityType uint16 = 9 + DocumentEntityType uint16 = 10 + UserEntityType uint16 = 11 + SessionEntityType uint16 = 12 + EmailEntityType uint16 = 13 + ControlEntityType uint16 = 14 + RiskEntityType uint16 = 15 + DocumentVersionEntityType uint16 = 16 + DocumentVersionSignatureEntityType uint16 = 17 + AssetEntityType uint16 = 18 + DatumEntityType uint16 = 19 + AuditEntityType uint16 = 20 + ReportEntityType uint16 = 21 + TrustCenterEntityType uint16 = 22 + TrustCenterAccessEntityType uint16 = 23 + VendorBusinessAssociateAgreementEntityType uint16 = 24 + FileEntityType uint16 = 25 + VendorContactEntityType uint16 = 26 + VendorDataPrivacyAgreementEntityType uint16 = 27 + NonconformityEntityType uint16 = 28 + ObligationEntityType uint16 = 29 + VendorServiceEntityType uint16 = 30 + SnapshotEntityType uint16 = 31 + ContinualImprovementEntityType uint16 = 32 + ProcessingActivityEntityType uint16 = 33 + ExportJobEntityType uint16 = 34 + TrustCenterReferenceEntityType uint16 = 35 + TrustCenterDocumentAccessEntityType uint16 = 36 + CustomDomainEntityType uint16 = 37 + InvitationEntityType uint16 = 38 + MembershipEntityType uint16 = 39 + SlackMessageEntityType uint16 = 40 + TrustCenterFileEntityType uint16 = 41 + SAMLConfigurationEntityType uint16 = 42 + UserAPIKeyEntityType uint16 = 43 + UserAPIKeyMembershipEntityType uint16 = 44 ) diff --git a/pkg/coredata/mesure_state.go b/pkg/coredata/mesure_state.go index e4b4f0ac3..832d945fb 100644 --- a/pkg/coredata/mesure_state.go +++ b/pkg/coredata/mesure_state.go @@ -30,6 +30,15 @@ const ( MeasureStateImplemented ) +func MeasureStates() []MeasureState { + return []MeasureState{ + MeasureStateNotStarted, + MeasureStateInProgress, + MeasureStateNotApplicable, + MeasureStateImplemented, + } +} + func (ms MeasureState) MarshalText() ([]byte, error) { return []byte(ms.String()), nil } diff --git a/pkg/coredata/nonconformity_status.go b/pkg/coredata/nonconformity_status.go index 4912fce35..73e226960 100644 --- a/pkg/coredata/nonconformity_status.go +++ b/pkg/coredata/nonconformity_status.go @@ -27,6 +27,14 @@ const ( NonconformityStatusClosed NonconformityStatus = "CLOSED" ) +func NonconformityStatuses() []NonconformityStatus { + return []NonconformityStatus{ + NonconformityStatusOpen, + NonconformityStatusInProgress, + NonconformityStatusClosed, + } +} + func (ncs NonconformityStatus) String() string { return string(ncs) } diff --git a/pkg/coredata/obligation_status.go b/pkg/coredata/obligation_status.go index 47a57d71e..36990e970 100644 --- a/pkg/coredata/obligation_status.go +++ b/pkg/coredata/obligation_status.go @@ -27,6 +27,14 @@ const ( ObligationStatusCompliant ObligationStatus = "COMPLIANT" ) +func ObligationStatuses() []ObligationStatus { + return []ObligationStatus{ + ObligationStatusNonCompliant, + ObligationStatusPartiallyCompliant, + ObligationStatusCompliant, + } +} + func (os ObligationStatus) String() string { return string(os) } diff --git a/pkg/coredata/people_kind.go b/pkg/coredata/people_kind.go index c9fd54c84..06fed1bec 100644 --- a/pkg/coredata/people_kind.go +++ b/pkg/coredata/people_kind.go @@ -29,6 +29,14 @@ const ( PeopleKindServiceAccount ) +func PeopleKinds() []PeopleKind { + return []PeopleKind{ + PeopleKindEmployee, + PeopleKindContractor, + PeopleKindServiceAccount, + } +} + func (ps PeopleKind) MarshalText() ([]byte, error) { return []byte(ps.String()), nil } diff --git a/pkg/coredata/processing_activities.go b/pkg/coredata/processing_activities.go index f8485be4c..ab372d2f3 100644 --- a/pkg/coredata/processing_activities.go +++ b/pkg/coredata/processing_activities.go @@ -20,10 +20,10 @@ import ( "maps" "time" - "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/page" "github.com/jackc/pgx/v5" "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/page" ) type ( @@ -36,13 +36,13 @@ type ( Purpose *string `db:"purpose"` DataSubjectCategory *string `db:"data_subject_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"` LawfulBasis ProcessingActivityLawfulBasis `db:"lawful_basis"` Recipients *string `db:"recipients"` Location *string `db:"location"` InternationalTransfers bool `db:"international_transfers"` - TransferSafeguards *ProcessingActivityTransferSafeguards `db:"transfer_safeguards"` + TransferSafeguard *ProcessingActivityTransferSafeguard `db:"transfer_safeguards"` RetentionPeriod *string `db:"retention_period"` SecurityMeasures *string `db:"security_measures"` DataProtectionImpactAssessment ProcessingActivityDataProtectionImpactAssessment `db:"data_protection_impact_assessment"` @@ -290,7 +290,7 @@ INSERT INTO processing_activities ( "recipients": p.Recipients, "location": p.Location, "international_transfers": p.InternationalTransfers, - "transfer_safeguards": p.TransferSafeguards, + "transfer_safeguards": p.TransferSafeguard, "retention_period": p.RetentionPeriod, "security_measures": p.SecurityMeasures, "data_protection_impact_assessment": p.DataProtectionImpactAssessment, @@ -351,7 +351,7 @@ WHERE "recipients": p.Recipients, "location": p.Location, "international_transfers": p.InternationalTransfers, - "transfer_safeguards": p.TransferSafeguards, + "transfer_safeguards": p.TransferSafeguard, "retention_period": p.RetentionPeriod, "security_measures": p.SecurityMeasures, "data_protection_impact_assessment": p.DataProtectionImpactAssessment, diff --git a/pkg/coredata/processing_activity_data_protection_impact_assessment.go b/pkg/coredata/processing_activity_data_protection_impact_assessment.go index cab071789..dc7d4ceb3 100644 --- a/pkg/coredata/processing_activity_data_protection_impact_assessment.go +++ b/pkg/coredata/processing_activity_data_protection_impact_assessment.go @@ -26,6 +26,13 @@ const ( ProcessingActivityDataProtectionImpactAssessmentNotNeeded ProcessingActivityDataProtectionImpactAssessment = "NOT_NEEDED" ) +func ProcessingActivityDataProtectionImpactAssessments() []ProcessingActivityDataProtectionImpactAssessment { + return []ProcessingActivityDataProtectionImpactAssessment{ + ProcessingActivityDataProtectionImpactAssessmentNeeded, + ProcessingActivityDataProtectionImpactAssessmentNotNeeded, + } +} + func (p ProcessingActivityDataProtectionImpactAssessment) String() string { return string(p) } diff --git a/pkg/coredata/processing_activity_lawful_basis.go b/pkg/coredata/processing_activity_lawful_basis.go index 903f879d6..477cb5102 100644 --- a/pkg/coredata/processing_activity_lawful_basis.go +++ b/pkg/coredata/processing_activity_lawful_basis.go @@ -30,6 +30,17 @@ const ( ProcessingActivityLawfulBasisPublicTask ProcessingActivityLawfulBasis = "PUBLIC_TASK" ) +func ProcessingActivityLawfulBases() []ProcessingActivityLawfulBasis { + return []ProcessingActivityLawfulBasis{ + ProcessingActivityLawfulBasisLegitimateInterest, + ProcessingActivityLawfulBasisConsent, + ProcessingActivityLawfulBasisContractualNecessity, + ProcessingActivityLawfulBasisLegalObligation, + ProcessingActivityLawfulBasisVitalInterests, + ProcessingActivityLawfulBasisPublicTask, + } +} + func (p ProcessingActivityLawfulBasis) String() string { return string(p) } diff --git a/pkg/coredata/processing_activity_special_or_criminal_data.go b/pkg/coredata/processing_activity_special_or_criminal_data.go index 0579c5426..88362a6e3 100644 --- a/pkg/coredata/processing_activity_special_or_criminal_data.go +++ b/pkg/coredata/processing_activity_special_or_criminal_data.go @@ -19,19 +19,27 @@ import ( "fmt" ) -type ProcessingActivitySpecialOrCriminalData string +type ProcessingActivitySpecialOrCriminalDatum string const ( - ProcessingActivitySpecialOrCriminalDataYes ProcessingActivitySpecialOrCriminalData = "YES" - ProcessingActivitySpecialOrCriminalDataNo ProcessingActivitySpecialOrCriminalData = "NO" - ProcessingActivitySpecialOrCriminalDataPossible ProcessingActivitySpecialOrCriminalData = "POSSIBLE" + ProcessingActivitySpecialOrCriminalDatumYes ProcessingActivitySpecialOrCriminalDatum = "YES" + ProcessingActivitySpecialOrCriminalDatumNo ProcessingActivitySpecialOrCriminalDatum = "NO" + ProcessingActivitySpecialOrCriminalDatumPossible ProcessingActivitySpecialOrCriminalDatum = "POSSIBLE" ) -func (p ProcessingActivitySpecialOrCriminalData) String() string { +func ProcessingActivitySpecialOrCriminalData() []ProcessingActivitySpecialOrCriminalDatum { + return []ProcessingActivitySpecialOrCriminalDatum{ + ProcessingActivitySpecialOrCriminalDatumYes, + ProcessingActivitySpecialOrCriminalDatumNo, + ProcessingActivitySpecialOrCriminalDatumPossible, + } +} + +func (p ProcessingActivitySpecialOrCriminalDatum) String() string { return string(p) } -func (p *ProcessingActivitySpecialOrCriminalData) Scan(value any) error { +func (p *ProcessingActivitySpecialOrCriminalDatum) Scan(value any) error { var s string switch v := value.(type) { case string: @@ -39,22 +47,22 @@ func (p *ProcessingActivitySpecialOrCriminalData) Scan(value any) error { case []byte: s = string(v) default: - return fmt.Errorf("unsupported type for ProcessingActivitySpecialOrCriminalData: %T", value) + return fmt.Errorf("unsupported type for ProcessingActivitySpecialOrCriminalDatum: %T", value) } switch s { case "YES": - *p = ProcessingActivitySpecialOrCriminalDataYes + *p = ProcessingActivitySpecialOrCriminalDatumYes case "NO": - *p = ProcessingActivitySpecialOrCriminalDataNo + *p = ProcessingActivitySpecialOrCriminalDatumNo case "POSSIBLE": - *p = ProcessingActivitySpecialOrCriminalDataPossible + *p = ProcessingActivitySpecialOrCriminalDatumPossible default: - return fmt.Errorf("invalid ProcessingActivitySpecialOrCriminalData value: %q", s) + return fmt.Errorf("invalid ProcessingActivitySpecialOrCriminalDatum value: %q", s) } return nil } -func (p ProcessingActivitySpecialOrCriminalData) Value() (driver.Value, error) { +func (p ProcessingActivitySpecialOrCriminalDatum) Value() (driver.Value, error) { return p.String(), nil } diff --git a/pkg/coredata/processing_activity_transfer_impact_assessment.go b/pkg/coredata/processing_activity_transfer_impact_assessment.go index 5ed39ab5b..00e09aa0d 100644 --- a/pkg/coredata/processing_activity_transfer_impact_assessment.go +++ b/pkg/coredata/processing_activity_transfer_impact_assessment.go @@ -26,6 +26,13 @@ const ( ProcessingActivityTransferImpactAssessmentNotNeeded ProcessingActivityTransferImpactAssessment = "NOT_NEEDED" ) +func ProcessingActivityTransferImpactAssessments() []ProcessingActivityTransferImpactAssessment { + return []ProcessingActivityTransferImpactAssessment{ + ProcessingActivityTransferImpactAssessmentNeeded, + ProcessingActivityTransferImpactAssessmentNotNeeded, + } +} + func (p ProcessingActivityTransferImpactAssessment) String() string { return string(p) } diff --git a/pkg/coredata/processing_activity_transfer_safeguards.go b/pkg/coredata/processing_activity_transfer_safeguards.go index 1da5bb3bd..4aab6e3b5 100644 --- a/pkg/coredata/processing_activity_transfer_safeguards.go +++ b/pkg/coredata/processing_activity_transfer_safeguards.go @@ -19,22 +19,33 @@ import ( "fmt" ) -type ProcessingActivityTransferSafeguards string +type ProcessingActivityTransferSafeguard string const ( - ProcessingActivityTransferSafeguardsStandardContractualClauses ProcessingActivityTransferSafeguards = "STANDARD_CONTRACTUAL_CLAUSES" - ProcessingActivityTransferSafeguardsBindingCorporateRules ProcessingActivityTransferSafeguards = "BINDING_CORPORATE_RULES" - ProcessingActivityTransferSafeguardsAdequacyDecision ProcessingActivityTransferSafeguards = "ADEQUACY_DECISION" - ProcessingActivityTransferSafeguardsDerogations ProcessingActivityTransferSafeguards = "DEROGATIONS" - ProcessingActivityTransferSafeguardsCodesOfConduct ProcessingActivityTransferSafeguards = "CODES_OF_CONDUCT" - ProcessingActivityTransferSafeguardsCertificationMechanisms ProcessingActivityTransferSafeguards = "CERTIFICATION_MECHANISMS" + ProcessingActivityTransferSafeguardStandardContractualClauses ProcessingActivityTransferSafeguard = "STANDARD_CONTRACTUAL_CLAUSES" + ProcessingActivityTransferSafeguardBindingCorporateRules ProcessingActivityTransferSafeguard = "BINDING_CORPORATE_RULES" + ProcessingActivityTransferSafeguardAdequacyDecision ProcessingActivityTransferSafeguard = "ADEQUACY_DECISION" + ProcessingActivityTransferSafeguardDerogations ProcessingActivityTransferSafeguard = "DEROGATIONS" + ProcessingActivityTransferSafeguardCodesOfConduct ProcessingActivityTransferSafeguard = "CODES_OF_CONDUCT" + 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) } -func (p *ProcessingActivityTransferSafeguards) Scan(value any) error { +func (p *ProcessingActivityTransferSafeguard) Scan(value any) error { var s string switch v := value.(type) { case string: @@ -42,28 +53,28 @@ func (p *ProcessingActivityTransferSafeguards) Scan(value any) error { case []byte: s = string(v) default: - return fmt.Errorf("unsupported type for ProcessingActivityTransferSafeguards: %T", value) + return fmt.Errorf("unsupported type for ProcessingActivityTransferSafeguard: %T", value) } switch s { case "STANDARD_CONTRACTUAL_CLAUSES": - *p = ProcessingActivityTransferSafeguardsStandardContractualClauses + *p = ProcessingActivityTransferSafeguardStandardContractualClauses case "BINDING_CORPORATE_RULES": - *p = ProcessingActivityTransferSafeguardsBindingCorporateRules + *p = ProcessingActivityTransferSafeguardBindingCorporateRules case "ADEQUACY_DECISION": - *p = ProcessingActivityTransferSafeguardsAdequacyDecision + *p = ProcessingActivityTransferSafeguardAdequacyDecision case "DEROGATIONS": - *p = ProcessingActivityTransferSafeguardsDerogations + *p = ProcessingActivityTransferSafeguardDerogations case "CODES_OF_CONDUCT": - *p = ProcessingActivityTransferSafeguardsCodesOfConduct + *p = ProcessingActivityTransferSafeguardCodesOfConduct case "CERTIFICATION_MECHANISMS": - *p = ProcessingActivityTransferSafeguardsCertificationMechanisms + *p = ProcessingActivityTransferSafeguardCertificationMechanisms default: - return fmt.Errorf("invalid ProcessingActivityTransferSafeguards value: %q", s) + return fmt.Errorf("invalid ProcessingActivityTransferSafeguard value: %q", s) } return nil } -func (p ProcessingActivityTransferSafeguards) Value() (driver.Value, error) { +func (p ProcessingActivityTransferSafeguard) Value() (driver.Value, error) { return p.String(), nil } diff --git a/pkg/coredata/risk_treatment.go b/pkg/coredata/risk_treatment.go index d8bb13430..98c690543 100644 --- a/pkg/coredata/risk_treatment.go +++ b/pkg/coredata/risk_treatment.go @@ -30,6 +30,15 @@ const ( RiskTreatmentTransferred RiskTreatment = "TRANSFERRED" ) +func RiskTreatments() []RiskTreatment { + return []RiskTreatment{ + RiskTreatmentMitigated, + RiskTreatmentAccepted, + RiskTreatmentAvoided, + RiskTreatmentTransferred, + } +} + func (rt RiskTreatment) MarshalText() ([]byte, error) { return []byte(rt.String()), nil } diff --git a/pkg/coredata/snapshots_type.go b/pkg/coredata/snapshots_type.go index 8995412d6..1a091325c 100644 --- a/pkg/coredata/snapshots_type.go +++ b/pkg/coredata/snapshots_type.go @@ -34,6 +34,16 @@ const ( SnapshotsTypeProcessingActivities SnapshotsType = "PROCESSING_ACTIVITIES" ) +func SnapshotsTypes() []SnapshotsType { + return []SnapshotsType{ + SnapshotsTypeRisks, + SnapshotsTypeVendors, + SnapshotsTypeAssets, + SnapshotsTypeData, + SnapshotsTypeNonconformities, + } +} + func (st SnapshotsType) String() string { return string(st) } diff --git a/pkg/coredata/task_state.go b/pkg/coredata/task_state.go index 4a8d25c18..3ada0a0b6 100644 --- a/pkg/coredata/task_state.go +++ b/pkg/coredata/task_state.go @@ -28,6 +28,13 @@ const ( TaskStateDone ) +func TaskStates() []TaskState { + return []TaskState{ + TaskStateTodo, + TaskStateDone, + } +} + func (ts TaskState) MarshalText() ([]byte, error) { return []byte(ts.String()), nil } diff --git a/pkg/coredata/trust_center_visibility.go b/pkg/coredata/trust_center_visibility.go index 90dc066d2..996b30438 100644 --- a/pkg/coredata/trust_center_visibility.go +++ b/pkg/coredata/trust_center_visibility.go @@ -27,6 +27,14 @@ const ( TrustCenterVisibilityPublic TrustCenterVisibility = "PUBLIC" ) +func TrustCenterVisibilities() []TrustCenterVisibility { + return []TrustCenterVisibility{ + TrustCenterVisibilityNone, + TrustCenterVisibilityPrivate, + TrustCenterVisibilityPublic, + } +} + func (tcv TrustCenterVisibility) String() string { return string(tcv) } diff --git a/pkg/coredata/vendor_category.go b/pkg/coredata/vendor_category.go index c852846f8..62fdc637f 100644 --- a/pkg/coredata/vendor_category.go +++ b/pkg/coredata/vendor_category.go @@ -47,6 +47,33 @@ const ( 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 { return string(i) } diff --git a/pkg/probo/asset_service.go b/pkg/probo/asset_service.go index 06be918c5..d751ab82f 100644 --- a/pkg/probo/asset_service.go +++ b/pkg/probo/asset_service.go @@ -5,10 +5,11 @@ import ( "fmt" "time" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) type AssetService struct { @@ -35,6 +36,38 @@ type UpdateAssetRequest struct { 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( ctx context.Context, assetID gid.GID, @@ -135,6 +168,10 @@ func (s AssetService) Update( ctx context.Context, req UpdateAssetRequest, ) (*coredata.Asset, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + now := time.Now() asset := &coredata.Asset{ID: req.ID} assetVendors := &coredata.AssetVendors{} @@ -185,6 +222,10 @@ func (s AssetService) Create( ctx context.Context, req CreateAssetRequest, ) (*coredata.Asset, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + now := time.Now() assetID := gid.New(s.svc.scope.GetTenantID(), coredata.AssetEntityType) assetVendors := &coredata.AssetVendors{} diff --git a/pkg/probo/audit_service.go b/pkg/probo/audit_service.go index 28c110419..90ba148c6 100644 --- a/pkg/probo/audit_service.go +++ b/pkg/probo/audit_service.go @@ -21,11 +21,12 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "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/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/crypto/uuid" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) type AuditService struct { @@ -52,21 +53,45 @@ type ( TrustCenterVisibility *coredata.TrustCenterVisibility } - UpdateAuditStateRequest struct { - ID gid.GID - State coredata.AuditState - } - UploadAuditReportRequest struct { AuditID gid.GID 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( ctx context.Context, auditID gid.GID, @@ -111,8 +136,11 @@ func (s *AuditService) Create( ctx context.Context, req *CreateAuditRequest, ) (*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{ ID: gid.New(s.svc.scope.GetTenantID(), coredata.AuditEntityType), Name: req.Name, @@ -166,8 +194,11 @@ func (s *AuditService) Update( ctx context.Context, req *UpdateAuditRequest, ) (*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( ctx, func(conn pg.Conn) error { diff --git a/pkg/probo/connector_service.go b/pkg/probo/connector_service.go index 0d9e4eb84..62b589cdf 100644 --- a/pkg/probo/connector_service.go +++ b/pkg/probo/connector_service.go @@ -22,11 +22,12 @@ import ( "text/template" "time" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/connector" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, organizationID gid.GID, @@ -88,20 +98,8 @@ func (s *ConnectorService) Create( ctx context.Context, req CreateConnectorRequest, ) (*coredata.Connector, error) { - if req.OrganizationID == gid.Nil { - return nil, fmt.Errorf("organization ID is required") - } - - 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") + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) } id := gid.New(s.svc.scope.GetTenantID(), coredata.ConnectorEntityType) diff --git a/pkg/probo/continual_improvement_service.go b/pkg/probo/continual_improvement_service.go index 5b00bcb97..e5cca61b4 100644 --- a/pkg/probo/continual_improvement_service.go +++ b/pkg/probo/continual_improvement_service.go @@ -19,10 +19,11 @@ import ( "fmt" "time" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, continualImprovementID gid.GID, @@ -81,6 +110,10 @@ func (s *ContinualImprovementService) Create( ctx context.Context, req *CreateContinualImprovementRequest, ) (*coredata.ContinualImprovement, error) { + if err := req.Validate(); err != nil { + return nil, err + } + now := time.Now() improvement := &coredata.ContinualImprovement{ diff --git a/pkg/probo/control_service.go b/pkg/probo/control_service.go index df0c96e59..02072ee61 100644 --- a/pkg/probo/control_service.go +++ b/pkg/probo/control_service.go @@ -23,6 +23,7 @@ import ( "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/validator" ) type ( @@ -48,18 +49,35 @@ type ( Status *coredata.ControlStatus 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( ctx context.Context, documentID gid.GID, @@ -706,6 +724,10 @@ func (s ControlService) Create( ctx context.Context, req CreateControlRequest, ) (*coredata.Control, error) { + if err := req.Validate(); err != nil { + return nil, err + } + now := time.Now() framework := &coredata.Framework{} @@ -765,6 +787,10 @@ func (s ControlService) Update( ctx context.Context, req UpdateControlRequest, ) (*coredata.Control, error) { + if err := req.Validate(); err != nil { + return nil, err + } + control := &coredata.Control{ID: req.ID} err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error { diff --git a/pkg/probo/custom_domain_service.go b/pkg/probo/custom_domain_service.go index f00dbf56d..99d6ab7fe 100644 --- a/pkg/probo/custom_domain_service.go +++ b/pkg/probo/custom_domain_service.go @@ -18,12 +18,13 @@ import ( "context" "fmt" + "go.gearno.de/kit/log" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/certmanager" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/crypto/cipher" "go.probo.inc/probo/pkg/gid" - "go.gearno.de/kit/log" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) 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( svc *TenantService, acmeService *certmanager.ACMEService, @@ -58,6 +68,10 @@ func (s *CustomDomainService) CreateCustomDomain( ctx context.Context, req CreateCustomDomainRequest, ) (*coredata.CustomDomain, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + var domain *coredata.CustomDomain err := s.svc.pg.WithTx( diff --git a/pkg/probo/datum_service.go b/pkg/probo/datum_service.go index bb6191084..aa6eeca94 100644 --- a/pkg/probo/datum_service.go +++ b/pkg/probo/datum_service.go @@ -19,30 +19,61 @@ import ( "fmt" "time" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) -type DatumService struct { - svc *TenantService +type ( + 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 { - OrganizationID gid.GID - Name string - DataClassification coredata.DataClassification - OwnerID gid.GID - VendorIDs []gid.GID -} +func (udr *UpdateDatumRequest) Validate() error { + v := validator.New() -type UpdateDatumRequest struct { - ID gid.GID - Name *string - DataClassification *coredata.DataClassification - OwnerID *gid.GID - VendorIDs []gid.GID + v.Check(udr.ID, "id", validator.Required(), validator.GID(coredata.DatumEntityType)) + v.Check(udr.Name, "name", validator.SafeText(NameMaxLength)) + v.Check(udr.DataClassification, "data_classification", validator.OneOfSlice(coredata.DataClassifications())) + v.Check(udr.OwnerID, "owner_id", validator.GID(coredata.PeopleEntityType)) + v.CheckEach(udr.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 (s DatumService) Get( @@ -145,6 +176,10 @@ func (s DatumService) Update( ctx context.Context, req UpdateDatumRequest, ) (*coredata.Datum, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + now := time.Now() datum := &coredata.Datum{} datumVendors := &coredata.DatumVendors{} @@ -189,6 +224,10 @@ func (s DatumService) Create( ctx context.Context, req CreateDatumRequest, ) (*coredata.Datum, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + now := time.Now() datumID := gid.New(s.svc.scope.GetTenantID(), coredata.DatumEntityType) datumVendors := &coredata.DatumVendors{} diff --git a/pkg/probo/document_service.go b/pkg/probo/document_service.go index 441bbf244..b9acada6c 100644 --- a/pkg/probo/document_service.go +++ b/pkg/probo/document_service.go @@ -26,6 +26,7 @@ import ( "go.probo.inc/probo/pkg/html2pdf" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/statelesstoken" + "go.probo.inc/probo/pkg/validator" "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 ( TokenTypeSigningRequest = "signing_request" @@ -308,6 +345,10 @@ func (s *DocumentService) Create( ctx context.Context, req CreateDocumentRequest, ) (*coredata.Document, *coredata.DocumentVersion, error) { + if err := req.Validate(); err != nil { + return nil, nil, err + } + now := time.Now() documentID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentEntityType) documentVersionID := gid.New(s.svc.scope.GetTenantID(), coredata.DocumentVersionEntityType) @@ -1131,6 +1172,10 @@ func (s *DocumentService) Update( ctx context.Context, req UpdateDocumentRequest, ) (*coredata.Document, error) { + if err := req.Validate(); err != nil { + return nil, err + } + document := &coredata.Document{} people := &coredata.People{} now := time.Now() diff --git a/pkg/probo/evidence_service.go b/pkg/probo/evidence_service.go index 3e14177d0..617d314b7 100644 --- a/pkg/probo/evidence_service.go +++ b/pkg/probo/evidence_service.go @@ -19,12 +19,13 @@ import ( "fmt" "time" + "go.gearno.de/crypto/uuid" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/filevalidation" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/crypto/uuid" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, evidenceID gid.GID, @@ -68,6 +79,10 @@ func (s EvidenceService) UploadMeasureEvidence( ctx context.Context, req UploadMeasureEvidenceRequest, ) (*coredata.Evidence, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + now := time.Now() evidenceID := gid.New(s.svc.scope.GetTenantID(), coredata.EvidenceEntityType) diff --git a/pkg/probo/file_service.go b/pkg/probo/file_service.go index 8b6a00e70..c99dffb47 100644 --- a/pkg/probo/file_service.go +++ b/pkg/probo/file_service.go @@ -23,11 +23,11 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "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/filevalidation" "go.probo.inc/probo/pkg/gid" - "go.gearno.de/crypto/uuid" - "go.gearno.de/kit/pg" ) type ( diff --git a/pkg/probo/framework_service.go b/pkg/probo/framework_service.go index f7d1d9e0a..29dbf7e33 100644 --- a/pkg/probo/framework_service.go +++ b/pkg/probo/framework_service.go @@ -35,6 +35,7 @@ import ( "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/slug" "go.probo.inc/probo/pkg/soagen" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, frameworkID gid.GID, @@ -310,6 +331,10 @@ func (s FrameworkService) Create( ctx context.Context, req CreateFrameworkRequest, ) (*coredata.Framework, error) { + if err := req.Validate(); err != nil { + return nil, err + } + now := time.Now() organization := &coredata.Organization{} @@ -420,6 +445,10 @@ func (s FrameworkService) Update( ctx context.Context, req UpdateFrameworkRequest, ) (*coredata.Framework, error) { + if err := req.Validate(); err != nil { + return nil, err + } + framework := &coredata.Framework{ID: req.ID} err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error { diff --git a/pkg/probo/measure_service.go b/pkg/probo/measure_service.go index ae3e78fb4..20c3bebc9 100644 --- a/pkg/probo/measure_service.go +++ b/pkg/probo/measure_service.go @@ -24,6 +24,7 @@ import ( "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, riskID gid.GID, @@ -399,6 +423,10 @@ func (s MeasureService) Update( ctx context.Context, req UpdateMeasureRequest, ) (*coredata.Measure, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + measure := &coredata.Measure{ID: req.ID} err := s.svc.pg.WithTx( @@ -444,6 +472,10 @@ func (s MeasureService) Create( ctx context.Context, req CreateMeasureRequest, ) (*coredata.Measure, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + now := time.Now() var measure *coredata.Measure organization := &coredata.Organization{} diff --git a/pkg/probo/nonconformity_service.go b/pkg/probo/nonconformity_service.go index 2511c694d..14333b2f0 100644 --- a/pkg/probo/nonconformity_service.go +++ b/pkg/probo/nonconformity_service.go @@ -19,10 +19,11 @@ import ( "fmt" "time" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, nonconformityID gid.GID, @@ -83,6 +114,10 @@ func (s *NonconformityService) Create( ctx context.Context, req *CreateNonconformityRequest, ) (*coredata.Nonconformity, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + now := time.Now() nonconformity := &coredata.Nonconformity{ @@ -143,6 +178,10 @@ func (s *NonconformityService) Update( ctx context.Context, req *UpdateNonconformityRequest, ) (*coredata.Nonconformity, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + nonconformity := &coredata.Nonconformity{} err := s.svc.pg.WithTx( diff --git a/pkg/probo/obligation_service.go b/pkg/probo/obligation_service.go index 7720c4da9..9c27f3527 100644 --- a/pkg/probo/obligation_service.go +++ b/pkg/probo/obligation_service.go @@ -19,10 +19,11 @@ import ( "fmt" "time" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, obligationID gid.GID, @@ -85,6 +116,10 @@ func (s *ObligationService) Create( ctx context.Context, req *CreateObligationRequest, ) (*coredata.Obligation, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + now := time.Now() obligation := &coredata.Obligation{ @@ -135,6 +170,10 @@ func (s *ObligationService) Update( ctx context.Context, req *UpdateObligationRequest, ) (*coredata.Obligation, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + obligation := &coredata.Obligation{} err := s.svc.pg.WithTx( diff --git a/pkg/probo/organization_service.go b/pkg/probo/organization_service.go index 5932d72c7..6ff33c7c2 100644 --- a/pkg/probo/organization_service.go +++ b/pkg/probo/organization_service.go @@ -22,12 +22,13 @@ import ( "path/filepath" "time" + "go.gearno.de/crypto/uuid" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/filevalidation" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/slug" - "go.gearno.de/crypto/uuid" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, req CreateOrganizationRequest, ) (*coredata.Organization, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + now := time.Now() organizationID := gid.New(s.svc.scope.GetTenantID(), coredata.OrganizationEntityType) @@ -154,6 +182,10 @@ func (s OrganizationService) Update( ctx context.Context, req UpdateOrganizationRequest, ) (*coredata.Organization, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + organization := &coredata.Organization{} err := s.svc.pg.WithTx( diff --git a/pkg/probo/people_service.go b/pkg/probo/people_service.go index f6acc9a88..3b567895e 100644 --- a/pkg/probo/people_service.go +++ b/pkg/probo/people_service.go @@ -19,10 +19,11 @@ import ( "fmt" "time" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) type ( @@ -30,17 +31,6 @@ type ( 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 { OrganizationID gid.GID FullName string @@ -51,8 +41,53 @@ type ( ContractStartDate *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( ctx context.Context, peopleID gid.GID, @@ -133,6 +168,10 @@ func (s PeopleService) Update( ctx context.Context, req UpdatePeopleRequest, ) (*coredata.People, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + people := &coredata.People{} err := s.svc.pg.WithTx( @@ -191,10 +230,8 @@ func (s PeopleService) Create( ctx context.Context, req CreatePeopleRequest, ) (*coredata.People, error) { - if req.ContractStartDate != nil && req.ContractEndDate != nil { - if req.ContractEndDate.Before(*req.ContractStartDate) { - return nil, fmt.Errorf("contract end date must be after or equal to start date") - } + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) } now := time.Now() diff --git a/pkg/probo/processing_activity_service.go b/pkg/probo/processing_activity_service.go index a76a60a3a..e11fa14a7 100644 --- a/pkg/probo/processing_activity_service.go +++ b/pkg/probo/processing_activity_service.go @@ -19,10 +19,11 @@ import ( "fmt" "time" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) type ProcessingActivityService struct { @@ -36,13 +37,13 @@ type ( Purpose *string DataSubjectCategory *string PersonalDataCategory *string - SpecialOrCriminalData coredata.ProcessingActivitySpecialOrCriminalData + SpecialOrCriminalData coredata.ProcessingActivitySpecialOrCriminalDatum ConsentEvidenceLink *string LawfulBasis coredata.ProcessingActivityLawfulBasis Recipients *string Location *string InternationalTransfers bool - TransferSafeguards *coredata.ProcessingActivityTransferSafeguards + TransferSafeguard *coredata.ProcessingActivityTransferSafeguard RetentionPeriod *string SecurityMeasures *string DataProtectionImpactAssessment coredata.ProcessingActivityDataProtectionImpactAssessment @@ -56,13 +57,13 @@ type ( Purpose **string DataSubjectCategory **string PersonalDataCategory **string - SpecialOrCriminalData *coredata.ProcessingActivitySpecialOrCriminalData + SpecialOrCriminalData *coredata.ProcessingActivitySpecialOrCriminalDatum ConsentEvidenceLink **string LawfulBasis *coredata.ProcessingActivityLawfulBasis Recipients **string Location **string InternationalTransfers *bool - TransferSafeguards **coredata.ProcessingActivityTransferSafeguards + TransferSafeguard **coredata.ProcessingActivityTransferSafeguard RetentionPeriod **string SecurityMeasures **string 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( ctx context.Context, processingActivityID gid.GID, @@ -111,7 +163,7 @@ func (s *ProcessingActivityService) Create( Recipients: req.Recipients, Location: req.Location, InternationalTransfers: req.InternationalTransfers, - TransferSafeguards: req.TransferSafeguards, + TransferSafeguard: req.TransferSafeguard, RetentionPeriod: req.RetentionPeriod, SecurityMeasures: req.SecurityMeasures, DataProtectionImpactAssessment: req.DataProtectionImpactAssessment, @@ -193,8 +245,8 @@ func (s *ProcessingActivityService) Update( if req.InternationalTransfers != nil { processingActivity.InternationalTransfers = *req.InternationalTransfers } - if req.TransferSafeguards != nil { - processingActivity.TransferSafeguards = *req.TransferSafeguards + if req.TransferSafeguard != nil { + processingActivity.TransferSafeguard = *req.TransferSafeguard } if req.RetentionPeriod != nil { processingActivity.RetentionPeriod = *req.RetentionPeriod diff --git a/pkg/probo/risk_service.go b/pkg/probo/risk_service.go index 2991375a6..dcfc915ae 100644 --- a/pkg/probo/risk_service.go +++ b/pkg/probo/risk_service.go @@ -23,6 +23,7 @@ import ( "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, measureID gid.GID, diff --git a/pkg/probo/service.go b/pkg/probo/service.go index 1fdb55993..0f692c229 100644 --- a/pkg/probo/service.go +++ b/pkg/probo/service.go @@ -20,6 +20,9 @@ import ( "time" "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/auth" "go.probo.inc/probo/pkg/authz" @@ -30,9 +33,12 @@ import ( "go.probo.inc/probo/pkg/filevalidation" "go.probo.inc/probo/pkg/gid" "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 { diff --git a/pkg/probo/snapshot_service.go b/pkg/probo/snapshot_service.go index 36f498db8..b9dfb1e8e 100644 --- a/pkg/probo/snapshot_service.go +++ b/pkg/probo/snapshot_service.go @@ -19,10 +19,11 @@ import ( "fmt" "time" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, snapshotID gid.GID, diff --git a/pkg/probo/task_service.go b/pkg/probo/task_service.go index 5de2a810e..fbdbbde9b 100644 --- a/pkg/probo/task_service.go +++ b/pkg/probo/task_service.go @@ -24,6 +24,7 @@ import ( "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, req CreateTaskRequest, ) (*coredata.Task, error) { + if err := req.Validate(); err != nil { + return nil, err + } + now := time.Now() taskID := gid.New(s.svc.scope.GetTenantID(), coredata.TaskEntityType) @@ -180,6 +210,9 @@ func (s TaskService) Update( ctx context.Context, req UpdateTaskRequest, ) (*coredata.Task, error) { + if err := req.Validate(); err != nil { + return nil, err + } task := &coredata.Task{} diff --git a/pkg/probo/trust_center_access_service.go b/pkg/probo/trust_center_access_service.go index 437e0eccf..526d802bd 100644 --- a/pkg/probo/trust_center_access_service.go +++ b/pkg/probo/trust_center_access_service.go @@ -17,7 +17,6 @@ package probo import ( "context" "fmt" - "net/mail" "net/url" "time" @@ -27,6 +26,7 @@ import ( "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/statelesstoken" + "go.probo.inc/probo/pkg/validator" ) type ( @@ -49,16 +49,40 @@ type ( TrustCenterFileIDs []gid.GID } - DeleteTrustCenterAccessRequest struct { - ID gid.GID - } - TrustCenterAccessData struct { TrustCenterID gid.GID `json:"trust_center_id"` 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( ctx context.Context, trustCenterID gid.GID, @@ -239,18 +263,12 @@ func (s TrustCenterAccessService) Create( ctx context.Context, req *CreateTrustCenterAccessRequest, ) (*coredata.TrustCenterAccess, error) { - if _, err := mail.ParseAddress(req.Email); err != nil { - return nil, fmt.Errorf("invalid email address") - } - - if req.Name == "" { - return nil, fmt.Errorf("name is required") + if err := req.Validate(); err != nil { + return nil, err } now := time.Now() - var access *coredata.TrustCenterAccess - err := s.svc.pg.WithTx( ctx, func(tx pg.Conn) error { @@ -285,14 +303,13 @@ func (s TrustCenterAccessService) Update( ctx context.Context, req *UpdateTrustCenterAccessRequest, ) (*coredata.TrustCenterAccess, error) { - now := time.Now() - var access *coredata.TrustCenterAccess - - if req.Name != nil && *req.Name == "" { - return nil, fmt.Errorf("name is required") + if err := req.Validate(); err != nil { + return nil, err } + now := time.Now() + var access *coredata.TrustCenterAccess err := s.svc.pg.WithTx( ctx, func(tx pg.Conn) error { @@ -344,14 +361,14 @@ func (s TrustCenterAccessService) Update( func (s TrustCenterAccessService) Delete( ctx context.Context, - req *DeleteTrustCenterAccessRequest, + trustCenterAccessID gid.GID, ) error { err := s.svc.pg.WithTx( ctx, func(tx pg.Conn) error { 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) } diff --git a/pkg/probo/trust_center_file_service.go b/pkg/probo/trust_center_file_service.go index aa66a7342..8a405ab33 100644 --- a/pkg/probo/trust_center_file_service.go +++ b/pkg/probo/trust_center_file_service.go @@ -25,12 +25,13 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "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/filevalidation" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/crypto/uuid" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) type ( @@ -53,12 +54,31 @@ type ( Category *string 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( ctx context.Context, organizationID gid.GID, @@ -134,8 +154,8 @@ func (s TrustCenterFileService) Create( ctx context.Context, req *CreateTrustCenterFileRequest, ) (*coredata.TrustCenterFile, error) { - if req.Name == "" { - return nil, fmt.Errorf("name is required") + if err := req.Validate(); err != nil { + return nil, err } // Validate file @@ -197,14 +217,14 @@ func (s TrustCenterFileService) Update( ctx context.Context, req *UpdateTrustCenterFileRequest, ) (*coredata.TrustCenterFile, error) { + if err := req.Validate(); err != nil { + return nil, err + } + now := time.Now() var file *coredata.TrustCenterFile - if req.Name != nil && *req.Name == "" { - return nil, fmt.Errorf("name is required") - } - err := s.svc.pg.WithTx( ctx, func(tx pg.Conn) error { @@ -242,14 +262,14 @@ func (s TrustCenterFileService) Update( func (s TrustCenterFileService) Delete( ctx context.Context, - req *DeleteTrustCenterFileRequest, + trustCenterFileID gid.GID, ) error { err := s.svc.pg.WithTx( ctx, func(tx pg.Conn) error { 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) } diff --git a/pkg/probo/trust_center_reference_service.go b/pkg/probo/trust_center_reference_service.go index 2bd524dd6..48ec8ce4f 100644 --- a/pkg/probo/trust_center_reference_service.go +++ b/pkg/probo/trust_center_reference_service.go @@ -31,6 +31,7 @@ import ( "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" + "go.probo.inc/probo/pkg/validator" ) type ( @@ -54,12 +55,30 @@ type ( LogoFile *File 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( ctx context.Context, trustCenterID gid.GID, @@ -132,12 +151,8 @@ func (s TrustCenterReferenceService) Create( ctx context.Context, req *CreateTrustCenterReferenceRequest, ) (*coredata.TrustCenterReference, error) { - if req.Name == "" { - return nil, fmt.Errorf("name is required") - } - - if req.WebsiteURL == "" { - return nil, fmt.Errorf("website URL is required") + if err := req.Validate(); err != nil { + return nil, err } now := time.Now() @@ -185,19 +200,14 @@ func (s TrustCenterReferenceService) Update( ctx context.Context, req *UpdateTrustCenterReferenceRequest, ) (*coredata.TrustCenterReference, error) { + if err := req.Validate(); err != nil { + return nil, err + } + now := time.Now() var reference *coredata.TrustCenterReference 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 err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error { @@ -254,12 +264,12 @@ func (s TrustCenterReferenceService) Update( func (s TrustCenterReferenceService) Delete( ctx context.Context, - req *DeleteTrustCenterReferenceRequest, + trustCenterReferenceID gid.GID, ) error { err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error { 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) } diff --git a/pkg/probo/trust_center_service.go b/pkg/probo/trust_center_service.go index 037444430..0c814e13b 100644 --- a/pkg/probo/trust_center_service.go +++ b/pkg/probo/trust_center_service.go @@ -25,10 +25,11 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "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/kit/pg" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/validator" ) type ( @@ -48,12 +49,27 @@ type ( File io.Reader 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( ctx context.Context, trustCenterID gid.GID, @@ -124,6 +140,10 @@ func (s TrustCenterService) Update( ctx context.Context, req *UpdateTrustCenterRequest, ) (*coredata.TrustCenter, *coredata.File, error) { + if err := req.Validate(); err != nil { + return nil, nil, err + } + var trustCenter *coredata.TrustCenter var file *coredata.File @@ -170,6 +190,10 @@ func (s TrustCenterService) UploadNDA( ctx context.Context, req *UploadTrustCenterNDARequest, ) (*coredata.TrustCenter, *coredata.File, error) { + if err := req.Validate(); err != nil { + return nil, nil, err + } + objectKey, err := uuid.NewV7() if err != nil { return nil, nil, fmt.Errorf("cannot generate object key: %w", err) @@ -249,7 +273,7 @@ func (s TrustCenterService) UploadNDA( func (s TrustCenterService) DeleteNDA( ctx context.Context, - req *DeleteTrustCenterNDARequest, + trustCenterID gid.GID, ) (*coredata.TrustCenter, *coredata.File, error) { var trustCenter *coredata.TrustCenter @@ -257,7 +281,7 @@ func (s TrustCenterService) DeleteNDA( ctx, func(conn pg.Conn) error { 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) } @@ -276,7 +300,7 @@ func (s TrustCenterService) DeleteNDA( return nil, nil, err } - return trustCenter, nil, nil // File is nil after deletion + return trustCenter, nil, nil } func (s TrustCenterService) GenerateNDAFileURL( diff --git a/pkg/probo/vendor_business_associate_agreement_service.go b/pkg/probo/vendor_business_associate_agreement_service.go index 69b026091..543156d75 100644 --- a/pkg/probo/vendor_business_associate_agreement_service.go +++ b/pkg/probo/vendor_business_associate_agreement_service.go @@ -25,10 +25,11 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "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/kit/pg" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, vendorID gid.GID, @@ -85,6 +103,10 @@ func (s VendorBusinessAssociateAgreementService) Upload( vendorID gid.GID, req *VendorBusinessAssociateAgreementCreateRequest, ) (*coredata.VendorBusinessAssociateAgreement, *coredata.File, error) { + if err := req.Validate(); err != nil { + return nil, nil, err + } + objectKey, err := uuid.NewV7() if err != nil { return nil, nil, fmt.Errorf("cannot generate object key: %w", err) @@ -255,6 +277,10 @@ func (s VendorBusinessAssociateAgreementService) Update( vendorID gid.GID, req *VendorBusinessAssociateAgreementUpdateRequest, ) (*coredata.VendorBusinessAssociateAgreement, *coredata.File, error) { + if err := req.Validate(); err != nil { + return nil, nil, err + } + existingAgreement := &coredata.VendorBusinessAssociateAgreement{} file := &coredata.File{} diff --git a/pkg/probo/vendor_compliance_report_service.go b/pkg/probo/vendor_compliance_report_service.go index 36b5da3db..fb7d590a4 100644 --- a/pkg/probo/vendor_compliance_report_service.go +++ b/pkg/probo/vendor_compliance_report_service.go @@ -19,11 +19,12 @@ import ( "fmt" "time" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/filevalidation" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, vendorID gid.GID, @@ -66,6 +75,10 @@ func (s VendorComplianceReportService) Upload( vendorID gid.GID, req *VendorComplianceReportCreateRequest, ) (*coredata.VendorComplianceReport, error) { + if err := req.Validate(); err != nil { + return nil, err + } + vendor, err := s.svc.Vendors.Get(ctx, vendorID) if err != nil { return nil, fmt.Errorf("cannot get vendor: %w", err) diff --git a/pkg/probo/vendor_contact_service.go b/pkg/probo/vendor_contact_service.go index f0c46d0be..3c81d3466 100644 --- a/pkg/probo/vendor_contact_service.go +++ b/pkg/probo/vendor_contact_service.go @@ -19,10 +19,11 @@ import ( "fmt" "time" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, vendorContactID gid.GID, @@ -102,6 +127,10 @@ func (s VendorContactService) Create( ctx context.Context, req CreateVendorContactRequest, ) (*coredata.VendorContact, error) { + if err := req.Validate(); err != nil { + return nil, err + } + now := time.Now() vendorContact := &coredata.VendorContact{ ID: gid.New(s.svc.scope.GetTenantID(), coredata.VendorContactEntityType), @@ -136,6 +165,10 @@ func (s VendorContactService) Update( ctx context.Context, req UpdateVendorContactRequest, ) (*coredata.VendorContact, error) { + if err := req.Validate(); err != nil { + return nil, err + } + vendorContact := &coredata.VendorContact{} err := s.svc.pg.WithTx( diff --git a/pkg/probo/vendor_data_privacy_agreement_service.go b/pkg/probo/vendor_data_privacy_agreement_service.go index eb9d7d085..bfe2c1d39 100644 --- a/pkg/probo/vendor_data_privacy_agreement_service.go +++ b/pkg/probo/vendor_data_privacy_agreement_service.go @@ -25,10 +25,11 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "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/kit/pg" + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, vendorID gid.GID, @@ -85,6 +103,10 @@ func (s VendorDataPrivacyAgreementService) Upload( vendorID gid.GID, req *VendorDataPrivacyAgreementCreateRequest, ) (*coredata.VendorDataPrivacyAgreement, *coredata.File, error) { + if err := req.Validate(); err != nil { + return nil, nil, err + } + objectKey, err := uuid.NewV7() if err != nil { return nil, nil, fmt.Errorf("cannot generate object key: %w", err) @@ -253,6 +275,10 @@ func (s VendorDataPrivacyAgreementService) Update( vendorID gid.GID, req *VendorDataPrivacyAgreementUpdateRequest, ) (*coredata.VendorDataPrivacyAgreement, *coredata.File, error) { + if err := req.Validate(); err != nil { + return nil, nil, err + } + existingAgreement := &coredata.VendorDataPrivacyAgreement{} file := &coredata.File{} diff --git a/pkg/probo/vendor_service.go b/pkg/probo/vendor_service.go index e1ab95c4a..bf14d0718 100644 --- a/pkg/probo/vendor_service.go +++ b/pkg/probo/vendor_service.go @@ -19,10 +19,11 @@ import ( "fmt" "time" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, organizationID gid.GID, @@ -208,6 +270,10 @@ func (s VendorService) Update( ctx context.Context, req UpdateVendorRequest, ) (*coredata.Vendor, error) { + if err := req.Validate(); err != nil { + return nil, err + } + vendor := &coredata.Vendor{} err := s.svc.pg.WithTx( @@ -373,6 +439,10 @@ func (s VendorService) Create( ctx context.Context, req CreateVendorRequest, ) (*coredata.Vendor, error) { + if err := req.Validate(); err != nil { + return nil, err + } + now := time.Now() vendor := &coredata.Vendor{ ID: gid.New(s.svc.scope.GetTenantID(), coredata.VendorEntityType), @@ -542,6 +612,10 @@ func (s VendorService) CreateRiskAssessment( ctx context.Context, req CreateVendorRiskAssessmentRequest, ) (*coredata.VendorRiskAssessment, error) { + if err := req.Validate(); err != nil { + return nil, err + } + vendorRiskAssessmentID := gid.New(s.svc.scope.GetTenantID(), coredata.VendorRiskAssessmentEntityType) now := time.Now() diff --git a/pkg/probo/vendor_service_service.go b/pkg/probo/vendor_service_service.go index a89bc220a..b87789237 100644 --- a/pkg/probo/vendor_service_service.go +++ b/pkg/probo/vendor_service_service.go @@ -19,10 +19,11 @@ import ( "fmt" "time" + "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/gid" "go.probo.inc/probo/pkg/page" - "go.gearno.de/kit/pg" + "go.probo.inc/probo/pkg/validator" ) 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( ctx context.Context, vendorServiceID gid.GID, @@ -98,6 +119,10 @@ func (s VendorServiceService) Create( ctx context.Context, req CreateVendorServiceRequest, ) (*coredata.VendorService, error) { + if err := req.Validate(); err != nil { + return nil, err + } + now := time.Now() vendorService := &coredata.VendorService{ ID: gid.New(s.svc.scope.GetTenantID(), coredata.VendorServiceEntityType), @@ -130,6 +155,10 @@ func (s VendorServiceService) Update( ctx context.Context, req UpdateVendorServiceRequest, ) (*coredata.VendorService, error) { + if err := req.Validate(); err != nil { + return nil, err + } + vendorService := &coredata.VendorService{} err := s.svc.pg.WithTx( diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index 97ca41a35..9c9e1b524 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -43,25 +43,16 @@ enum OrderDirection enum MeasureState @goModel(model: "go.probo.inc/probo/pkg/coredata.MeasureState") { NOT_STARTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotStarted" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotStarted") IN_PROGRESS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureStateInProgress" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateInProgress") NOT_APPLICABLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotApplicable" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotApplicable") IMPLEMENTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureStateImplemented" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateImplemented") } -enum TaskState - @goModel(model: "go.probo.inc/probo/pkg/coredata.TaskState") { +enum TaskState @goModel(model: "go.probo.inc/probo/pkg/coredata.TaskState") { TODO @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateTodo") DONE @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateDone") } @@ -69,43 +60,27 @@ enum TaskState enum EvidenceState @goModel(model: "go.probo.inc/probo/pkg/coredata.EvidenceState") { FULFILLED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.EvidenceStateFulfilled" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateFulfilled") REQUESTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.EvidenceStateRequested" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateRequested") } -enum PeopleKind - @goModel(model: "go.probo.inc/probo/pkg/coredata.PeopleKind") { - EMPLOYEE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleKindEmployee") +enum PeopleKind @goModel(model: "go.probo.inc/probo/pkg/coredata.PeopleKind") { + EMPLOYEE @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleKindEmployee") CONTRACTOR - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.PeopleKindContractor" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleKindContractor") SERVICE_ACCOUNT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.PeopleKindServiceAccount" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleKindServiceAccount") } enum InvitationStatus @goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationStatus") { PENDING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.InvitationStatusPending" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusPending") ACCEPTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.InvitationStatusAccepted" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusAccepted") EXPIRED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.InvitationStatusExpired" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusExpired") } 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 @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentStatus") { - DRAFT - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusDraft") + DRAFT @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusDraft") PUBLISHED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentStatusPublished" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusPublished") } enum EvidenceType @@ -134,49 +106,28 @@ enum EvidenceType enum RiskTreatment @goModel(model: "go.probo.inc/probo/pkg/coredata.RiskTreatment") { MITIGATED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentMitigated" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentMitigated") ACCEPTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAccepted" - ) - AVOIDED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAvoided" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAccepted") + AVOIDED @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAvoided") TRANSFERRED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentTransferred" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentTransferred") } -enum AuditState - @goModel(model: "go.probo.inc/probo/pkg/coredata.AuditState") { +enum AuditState @goModel(model: "go.probo.inc/probo/pkg/coredata.AuditState") { NOT_STARTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditStateNotStarted" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateNotStarted") IN_PROGRESS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditStateInProgress" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateInProgress") COMPLETED @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateCompleted") - REJECTED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateRejected") - OUTDATED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateOutdated") + REJECTED @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateRejected") + OUTDATED @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateOutdated") } enum SAMLEnforcementPolicy - @goModel( - model: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicy" - ) { - OFF - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOff" - ) + @goModel(model: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicy") { + OFF @goEnum(value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOff") OPTIONAL @goEnum( value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOptional" @@ -190,21 +141,14 @@ enum SAMLEnforcementPolicy enum UserAuthMethod @goModel(model: "go.probo.inc/probo/pkg/coredata.UserAuthMethod") { PASSWORD - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.UserAuthMethodPassword" - ) - SAML - @goEnum(value: "go.probo.inc/probo/pkg/coredata.UserAuthMethodSAML") + @goEnum(value: "go.probo.inc/probo/pkg/coredata.UserAuthMethodPassword") + SAML @goEnum(value: "go.probo.inc/probo/pkg/coredata.UserAuthMethodSAML") } enum TrustCenterVisibility - @goModel( - model: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibility" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibility") { NONE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityNone" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityNone") PRIVATE @goEnum( value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityPrivate" @@ -216,21 +160,14 @@ enum TrustCenterVisibility } enum NonconformityStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.NonconformityStatus" - ) { - OPEN - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusOpen" - ) + @goModel(model: "go.probo.inc/probo/pkg/coredata.NonconformityStatus") { + OPEN @goEnum(value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusOpen") IN_PROGRESS @goEnum( value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusInProgress" ) CLOSED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusClosed" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusClosed") } enum ObligationStatus @@ -244,9 +181,7 @@ enum ObligationStatus value: "go.probo.inc/probo/pkg/coredata.ObligationStatusPartiallyCompliant" ) COMPLIANT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ObligationStatusCompliant" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ObligationStatusCompliant") } enum ContinualImprovementStatus @@ -285,21 +220,21 @@ enum ContinualImprovementPriority ) } -enum ProcessingActivitySpecialOrCriminalData +enum ProcessingActivitySpecialOrCriminalDatum @goModel( - model: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalData" + model: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatum" ) { YES @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDataYes" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumYes" ) NO @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDataNo" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumNo" ) POSSIBLE @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( - model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguards" + model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguard" ) { STANDARD_CONTRACTUAL_CLAUSES @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsStandardContractualClauses" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardStandardContractualClauses" ) BINDING_CORPORATE_RULES @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsBindingCorporateRules" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardBindingCorporateRules" ) ADEQUACY_DECISION @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsAdequacyDecision" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardAdequacyDecision" ) DEROGATIONS @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsDerogations" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardDerogations" ) CODES_OF_CONDUCT @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsCodesOfConduct" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardCodesOfConduct" ) CERTIFICATION_MECHANISMS @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 @goModel(model: "go.probo.inc/probo/pkg/coredata.UserOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.UserOrderFieldCreatedAt" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.UserOrderFieldCreatedAt") } enum PeopleOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.PeopleOrderField") { FULL_NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldFullName" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldFullName") CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldCreatedAt" - ) - KIND - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldKind" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldCreatedAt") + KIND @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldKind") } enum VendorOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorOrderField") { - NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldName" - ) + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldName") CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldCreatedAt" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldCreatedAt") UPDATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldUpdatedAt" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldUpdatedAt") } enum FrameworkOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.FrameworkOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.FrameworkOrderField") { CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.FrameworkOrderFieldCreatedAt" @@ -445,9 +362,7 @@ enum FrameworkOrderField enum ControlOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.ControlOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldCreatedAt" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldCreatedAt") SECTION_TITLE @goEnum( value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldSectionTitle" @@ -457,13 +372,8 @@ enum ControlOrderField enum MeasureOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.MeasureOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldCreatedAt" - ) - NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldName" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldCreatedAt") + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldName") } enum TaskOrderField @@ -474,9 +384,7 @@ enum TaskOrderField enum DocumentOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentOrderField") { TITLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldTitle" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldTitle") CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldCreatedAt" @@ -490,23 +398,14 @@ enum DocumentOrderField enum RiskOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.RiskOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCreatedAt" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCreatedAt") UPDATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldUpdatedAt" - ) - NAME - @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldName") + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldUpdatedAt") + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldName") CATEGORY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCategory" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCategory") TREATMENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldTreatment" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldTreatment") INHERENT_RISK_SCORE @goEnum( value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldInherentRiskScore" @@ -541,9 +440,7 @@ enum VendorComplianceReportOrderField } enum VendorContactOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.VendorContactOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorContactOrderField") { CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorContactOrderFieldCreatedAt" @@ -559,9 +456,7 @@ enum VendorContactOrderField } enum VendorServiceOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderField") { CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderFieldCreatedAt" @@ -573,13 +468,9 @@ enum VendorServiceOrderField } enum OrganizationOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.OrganizationOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.OrganizationOrderField") { NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.OrganizationOrderFieldName" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.OrganizationOrderFieldName") CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.OrganizationOrderFieldCreatedAt" @@ -592,41 +483,25 @@ enum OrganizationOrderField enum DataSensitivity @goModel(model: "go.probo.inc/probo/pkg/coredata.DataSensitivity") { - NONE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityNone") - LOW - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityLow") - MEDIUM - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataSensitivityMedium" - ) - HIGH - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityHigh") + NONE @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityNone") + LOW @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityLow") + MEDIUM @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityMedium") + HIGH @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityHigh") CRITICAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataSensitivityCritical" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityCritical") } enum BusinessImpact @goModel(model: "go.probo.inc/probo/pkg/coredata.BusinessImpact") { LOW @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactLow") - MEDIUM - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.BusinessImpactMedium" - ) - HIGH - @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactHigh") + MEDIUM @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactMedium") + HIGH @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactHigh") CRITICAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.BusinessImpactCritical" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactCritical") } enum DocumentVersionOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderField") { VERSION @goEnum( value: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderFieldVersion" @@ -893,9 +768,7 @@ enum CountryCode enum VendorCategory @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorCategory") { ANALYTICS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics") CLOUD_MONITORING @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudMonitoring" @@ -925,28 +798,21 @@ enum VendorCategory value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEmployeeManagement" ) ENGINEERING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering") FINANCE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance") IDENTITY_PROVIDER @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIdentityProvider" ) IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIT") MARKETING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing") OFFICE_OPERATIONS @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOfficeOperations" ) - OTHER - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther") + OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther") PASSWORD_MANAGEMENT @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryPasswordManagement" @@ -960,15 +826,10 @@ enum VendorCategory value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProfessionalServices" ) RECRUITING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting" - ) - SALES - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales") + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting") + SALES @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales") SECURITY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity") VERSION_CONTROL @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryVersionControl" @@ -977,21 +838,15 @@ enum VendorCategory enum DocumentType @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentType") { - OTHER - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther") + OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther") ISMS @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeISMS") - POLICY - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy") + POLICY @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy") PROCEDURE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure") } enum DocumentClassification - @goModel( - model: "go.probo.inc/probo/pkg/coredata.DocumentClassification" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentClassification") { PUBLIC @goEnum( value: "go.probo.inc/probo/pkg/coredata.DocumentClassificationPublic" @@ -1010,34 +865,23 @@ enum DocumentClassification ) } -enum AssetType - @goModel(model: "go.probo.inc/probo/pkg/coredata.AssetType") { - PHYSICAL - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypePhysical") - VIRTUAL - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypeVirtual") +enum AssetType @goModel(model: "go.probo.inc/probo/pkg/coredata.AssetType") { + PHYSICAL @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypePhysical") + VIRTUAL @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypeVirtual") } enum AssetOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.AssetOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldCreatedAt" - ) - AMOUNT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldAmount" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldCreatedAt") + AMOUNT @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldAmount") } enum DatumOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.DatumOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldCreatedAt" - ) - NAME - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldName") + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldCreatedAt") + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldName") DATA_CLASSIFICATION @goEnum( value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldDataClassification" @@ -1047,59 +891,38 @@ enum DatumOrderField enum DataClassification @goModel(model: "go.probo.inc/probo/pkg/coredata.DataClassification") { PUBLIC - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataClassificationPublic" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataClassificationPublic") INTERNAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataClassificationInternal" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataClassificationInternal") CONFIDENTIAL @goEnum( value: "go.probo.inc/probo/pkg/coredata.DataClassificationConfidential" ) SECRET - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataClassificationSecret" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataClassificationSecret") } enum ControlStatus @goModel(model: "go.probo.inc/probo/pkg/coredata.ControlStatus") { INCLUDED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ControlStatusIncluded" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ControlStatusIncluded") EXCLUDED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ControlStatusExcluded" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ControlStatusExcluded") } enum AuditOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.AuditOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldCreatedAt" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldCreatedAt") VALID_FROM - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidFrom" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidFrom") VALID_UNTIL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidUntil" - ) - STATE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldState" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidUntil") + STATE @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldState") } enum NonconformityOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.NonconformityOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.NonconformityOrderField") { CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.NonconformityOrderFieldCreatedAt" @@ -1123,9 +946,7 @@ enum NonconformityOrderField } enum ObligationOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ObligationOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.ObligationOrderField") { CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldCreatedAt" @@ -1139,9 +960,7 @@ enum ObligationOrderField value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldDueDate" ) STATUS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldStatus" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldStatus") } enum ContinualImprovementOrderField @@ -1227,9 +1046,7 @@ enum TrustCenterReferenceOrderField } enum TrustCenterFileOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderField") { NAME @goEnum( value: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderFieldName" @@ -1246,24 +1063,16 @@ enum TrustCenterFileOrderField enum SnapshotsType @goModel(model: "go.probo.inc/probo/pkg/coredata.SnapshotsType") { - RISKS - @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeRisks") - VENDORS - @goEnum( - 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") + RISKS @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeRisks") + VENDORS @goEnum(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 @goEnum( value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeNonconformities" ) OBLIGATIONS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeObligations" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeObligations") CONTINUAL_IMPROVEMENTS @goEnum( value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeContinualImprovements" @@ -1280,20 +1089,12 @@ enum SnapshotOrderField @goEnum( value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldCreatedAt" ) - NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldName" - ) - TYPE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldType" - ) + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldName") + TYPE @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldType") } enum MembershipOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.MembershipOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipOrderField") { FULL_NAME @goEnum( value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldFullName" @@ -1303,9 +1104,7 @@ enum MembershipOrderField value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldEmailAddress" ) ROLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldRole" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldRole") CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldCreatedAt" @@ -1313,21 +1112,15 @@ enum MembershipOrderField } enum InvitationOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.InvitationOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationOrderField") { FULL_NAME @goEnum( value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldFullName" ) EMAIL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldEmail" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldEmail") ROLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldRole" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldRole") CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldCreatedAt" @@ -2347,13 +2140,13 @@ type ProcessingActivity implements Node { purpose: String dataSubjectCategory: String personalDataCategory: String - specialOrCriminalData: ProcessingActivitySpecialOrCriminalData! + specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum! consentEvidenceLink: String lawfulBasis: ProcessingActivityLawfulBasis! recipients: String location: String internationalTransfers: Boolean! - transferSafeguards: ProcessingActivityTransferSafeguards + transferSafeguards: ProcessingActivityTransferSafeguard retentionPeriod: String securityMeasures: String dataProtectionImpactAssessment: ProcessingActivityDataProtectionImpactAssessment! @@ -3920,13 +3713,13 @@ input CreateProcessingActivityInput { purpose: String dataSubjectCategory: String personalDataCategory: String - specialOrCriminalData: ProcessingActivitySpecialOrCriminalData! + specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum! consentEvidenceLink: String lawfulBasis: ProcessingActivityLawfulBasis! recipients: String location: String internationalTransfers: Boolean! - transferSafeguards: ProcessingActivityTransferSafeguards + transferSafeguards: ProcessingActivityTransferSafeguard retentionPeriod: String securityMeasures: String dataProtectionImpactAssessment: ProcessingActivityDataProtectionImpactAssessment! @@ -3940,13 +3733,13 @@ input UpdateProcessingActivityInput { purpose: String @goField(omittable: true) dataSubjectCategory: String @goField(omittable: true) personalDataCategory: String @goField(omittable: true) - specialOrCriminalData: ProcessingActivitySpecialOrCriminalData + specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum consentEvidenceLink: String lawfulBasis: ProcessingActivityLawfulBasis recipients: String @goField(omittable: true) location: String @goField(omittable: true) internationalTransfers: Boolean - transferSafeguards: ProcessingActivityTransferSafeguards + transferSafeguards: ProcessingActivityTransferSafeguard @goField(omittable: true) retentionPeriod: String @goField(omittable: true) securityMeasures: String @goField(omittable: true) @@ -4789,9 +4582,7 @@ type DeleteSnapshotPayload { } enum SSLStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatus" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatus") { PENDING @goEnum( value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusPending" diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index d0be73968..deff4c078 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -9784,25 +9784,16 @@ enum OrderDirection enum MeasureState @goModel(model: "go.probo.inc/probo/pkg/coredata.MeasureState") { NOT_STARTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotStarted" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotStarted") IN_PROGRESS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureStateInProgress" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateInProgress") NOT_APPLICABLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotApplicable" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateNotApplicable") IMPLEMENTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureStateImplemented" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureStateImplemented") } -enum TaskState - @goModel(model: "go.probo.inc/probo/pkg/coredata.TaskState") { +enum TaskState @goModel(model: "go.probo.inc/probo/pkg/coredata.TaskState") { TODO @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateTodo") DONE @goEnum(value: "go.probo.inc/probo/pkg/coredata.TaskStateDone") } @@ -9810,43 +9801,27 @@ enum TaskState enum EvidenceState @goModel(model: "go.probo.inc/probo/pkg/coredata.EvidenceState") { FULFILLED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.EvidenceStateFulfilled" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateFulfilled") REQUESTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.EvidenceStateRequested" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.EvidenceStateRequested") } -enum PeopleKind - @goModel(model: "go.probo.inc/probo/pkg/coredata.PeopleKind") { - EMPLOYEE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleKindEmployee") +enum PeopleKind @goModel(model: "go.probo.inc/probo/pkg/coredata.PeopleKind") { + EMPLOYEE @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleKindEmployee") CONTRACTOR - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.PeopleKindContractor" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleKindContractor") SERVICE_ACCOUNT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.PeopleKindServiceAccount" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleKindServiceAccount") } enum InvitationStatus @goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationStatus") { PENDING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.InvitationStatusPending" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusPending") ACCEPTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.InvitationStatusAccepted" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusAccepted") EXPIRED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.InvitationStatusExpired" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationStatusExpired") } enum Role @goModel(model: "go.probo.inc/probo/pkg/coredata.Role") { @@ -9858,12 +9833,9 @@ enum Role @goModel(model: "go.probo.inc/probo/pkg/coredata.Role") { enum DocumentStatus @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentStatus") { - DRAFT - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusDraft") + DRAFT @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusDraft") PUBLISHED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentStatusPublished" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentStatusPublished") } enum EvidenceType @@ -9875,49 +9847,28 @@ enum EvidenceType enum RiskTreatment @goModel(model: "go.probo.inc/probo/pkg/coredata.RiskTreatment") { MITIGATED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentMitigated" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentMitigated") ACCEPTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAccepted" - ) - AVOIDED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAvoided" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAccepted") + AVOIDED @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentAvoided") TRANSFERRED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentTransferred" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskTreatmentTransferred") } -enum AuditState - @goModel(model: "go.probo.inc/probo/pkg/coredata.AuditState") { +enum AuditState @goModel(model: "go.probo.inc/probo/pkg/coredata.AuditState") { NOT_STARTED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditStateNotStarted" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateNotStarted") IN_PROGRESS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditStateInProgress" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateInProgress") COMPLETED @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateCompleted") - REJECTED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateRejected") - OUTDATED - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateOutdated") + REJECTED @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateRejected") + OUTDATED @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditStateOutdated") } enum SAMLEnforcementPolicy - @goModel( - model: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicy" - ) { - OFF - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOff" - ) + @goModel(model: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicy") { + OFF @goEnum(value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOff") OPTIONAL @goEnum( value: "go.probo.inc/probo/pkg/coredata.SAMLEnforcementPolicyOptional" @@ -9931,21 +9882,14 @@ enum SAMLEnforcementPolicy enum UserAuthMethod @goModel(model: "go.probo.inc/probo/pkg/coredata.UserAuthMethod") { PASSWORD - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.UserAuthMethodPassword" - ) - SAML - @goEnum(value: "go.probo.inc/probo/pkg/coredata.UserAuthMethodSAML") + @goEnum(value: "go.probo.inc/probo/pkg/coredata.UserAuthMethodPassword") + SAML @goEnum(value: "go.probo.inc/probo/pkg/coredata.UserAuthMethodSAML") } enum TrustCenterVisibility - @goModel( - model: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibility" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibility") { NONE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityNone" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityNone") PRIVATE @goEnum( value: "go.probo.inc/probo/pkg/coredata.TrustCenterVisibilityPrivate" @@ -9957,21 +9901,14 @@ enum TrustCenterVisibility } enum NonconformityStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.NonconformityStatus" - ) { - OPEN - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusOpen" - ) + @goModel(model: "go.probo.inc/probo/pkg/coredata.NonconformityStatus") { + OPEN @goEnum(value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusOpen") IN_PROGRESS @goEnum( value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusInProgress" ) CLOSED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusClosed" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusClosed") } enum ObligationStatus @@ -9985,9 +9922,7 @@ enum ObligationStatus value: "go.probo.inc/probo/pkg/coredata.ObligationStatusPartiallyCompliant" ) COMPLIANT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ObligationStatusCompliant" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ObligationStatusCompliant") } enum ContinualImprovementStatus @@ -10026,21 +9961,21 @@ enum ContinualImprovementPriority ) } -enum ProcessingActivitySpecialOrCriminalData +enum ProcessingActivitySpecialOrCriminalDatum @goModel( - model: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalData" + model: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatum" ) { YES @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDataYes" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumYes" ) NO @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDataNo" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumNo" ) POSSIBLE @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDataPossible" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivitySpecialOrCriminalDatumPossible" ) } @@ -10074,33 +10009,33 @@ enum ProcessingActivityLawfulBasis ) } -enum ProcessingActivityTransferSafeguards +enum ProcessingActivityTransferSafeguard @goModel( - model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguards" + model: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguard" ) { STANDARD_CONTRACTUAL_CLAUSES @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsStandardContractualClauses" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardStandardContractualClauses" ) BINDING_CORPORATE_RULES @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsBindingCorporateRules" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardBindingCorporateRules" ) ADEQUACY_DECISION @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsAdequacyDecision" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardAdequacyDecision" ) DEROGATIONS @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsDerogations" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardDerogations" ) CODES_OF_CONDUCT @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsCodesOfConduct" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardCodesOfConduct" ) CERTIFICATION_MECHANISMS @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardsCertificationMechanisms" + value: "go.probo.inc/probo/pkg/coredata.ProcessingActivityTransferSafeguardCertificationMechanisms" ) } @@ -10136,47 +10071,29 @@ enum ProcessingActivityTransferImpactAssessment enum UserOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.UserOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.UserOrderFieldCreatedAt" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.UserOrderFieldCreatedAt") } enum PeopleOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.PeopleOrderField") { FULL_NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldFullName" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldFullName") CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldCreatedAt" - ) - KIND - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldKind" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldCreatedAt") + KIND @goEnum(value: "go.probo.inc/probo/pkg/coredata.PeopleOrderFieldKind") } enum VendorOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorOrderField") { - NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldName" - ) + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldName") CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldCreatedAt" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldCreatedAt") UPDATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldUpdatedAt" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorOrderFieldUpdatedAt") } enum FrameworkOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.FrameworkOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.FrameworkOrderField") { CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.FrameworkOrderFieldCreatedAt" @@ -10186,9 +10103,7 @@ enum FrameworkOrderField enum ControlOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.ControlOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldCreatedAt" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldCreatedAt") SECTION_TITLE @goEnum( value: "go.probo.inc/probo/pkg/coredata.ControlOrderFieldSectionTitle" @@ -10198,13 +10113,8 @@ enum ControlOrderField enum MeasureOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.MeasureOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldCreatedAt" - ) - NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldName" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldCreatedAt") + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.MeasureOrderFieldName") } enum TaskOrderField @@ -10215,9 +10125,7 @@ enum TaskOrderField enum DocumentOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentOrderField") { TITLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldTitle" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldTitle") CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.DocumentOrderFieldCreatedAt" @@ -10231,23 +10139,14 @@ enum DocumentOrderField enum RiskOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.RiskOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCreatedAt" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCreatedAt") UPDATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldUpdatedAt" - ) - NAME - @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldName") + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldUpdatedAt") + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldName") CATEGORY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCategory" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldCategory") TREATMENT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldTreatment" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldTreatment") INHERENT_RISK_SCORE @goEnum( value: "go.probo.inc/probo/pkg/coredata.RiskOrderFieldInherentRiskScore" @@ -10282,9 +10181,7 @@ enum VendorComplianceReportOrderField } enum VendorContactOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.VendorContactOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorContactOrderField") { CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorContactOrderFieldCreatedAt" @@ -10300,9 +10197,7 @@ enum VendorContactOrderField } enum VendorServiceOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderField") { CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorServiceOrderFieldCreatedAt" @@ -10314,13 +10209,9 @@ enum VendorServiceOrderField } enum OrganizationOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.OrganizationOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.OrganizationOrderField") { NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.OrganizationOrderFieldName" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.OrganizationOrderFieldName") CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.OrganizationOrderFieldCreatedAt" @@ -10333,41 +10224,25 @@ enum OrganizationOrderField enum DataSensitivity @goModel(model: "go.probo.inc/probo/pkg/coredata.DataSensitivity") { - NONE - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityNone") - LOW - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityLow") - MEDIUM - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataSensitivityMedium" - ) - HIGH - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityHigh") + NONE @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityNone") + LOW @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityLow") + MEDIUM @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityMedium") + HIGH @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityHigh") CRITICAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataSensitivityCritical" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataSensitivityCritical") } enum BusinessImpact @goModel(model: "go.probo.inc/probo/pkg/coredata.BusinessImpact") { LOW @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactLow") - MEDIUM - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.BusinessImpactMedium" - ) - HIGH - @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactHigh") + MEDIUM @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactMedium") + HIGH @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactHigh") CRITICAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.BusinessImpactCritical" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.BusinessImpactCritical") } enum DocumentVersionOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderField") { VERSION @goEnum( value: "go.probo.inc/probo/pkg/coredata.DocumentVersionOrderFieldVersion" @@ -10634,9 +10509,7 @@ enum CountryCode enum VendorCategory @goModel(model: "go.probo.inc/probo/pkg/coredata.VendorCategory") { ANALYTICS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryAnalytics") CLOUD_MONITORING @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryCloudMonitoring" @@ -10666,28 +10539,21 @@ enum VendorCategory value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEmployeeManagement" ) ENGINEERING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryEngineering") FINANCE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryFinance") IDENTITY_PROVIDER @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIdentityProvider" ) IT @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryIT") MARKETING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryMarketing") OFFICE_OPERATIONS @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOfficeOperations" ) - OTHER - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther") + OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryOther") PASSWORD_MANAGEMENT @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryPasswordManagement" @@ -10701,15 +10567,10 @@ enum VendorCategory value: "go.probo.inc/probo/pkg/coredata.VendorCategoryProfessionalServices" ) RECRUITING - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting" - ) - SALES - @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales") + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategoryRecruiting") + SALES @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySales") SECURITY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.VendorCategorySecurity") VERSION_CONTROL @goEnum( value: "go.probo.inc/probo/pkg/coredata.VendorCategoryVersionControl" @@ -10718,21 +10579,15 @@ enum VendorCategory enum DocumentType @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentType") { - OTHER - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther") + OTHER @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeOther") ISMS @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeISMS") - POLICY - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy") + POLICY @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypePolicy") PROCEDURE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DocumentTypeProcedure") } enum DocumentClassification - @goModel( - model: "go.probo.inc/probo/pkg/coredata.DocumentClassification" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.DocumentClassification") { PUBLIC @goEnum( value: "go.probo.inc/probo/pkg/coredata.DocumentClassificationPublic" @@ -10751,34 +10606,23 @@ enum DocumentClassification ) } -enum AssetType - @goModel(model: "go.probo.inc/probo/pkg/coredata.AssetType") { - PHYSICAL - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypePhysical") - VIRTUAL - @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypeVirtual") +enum AssetType @goModel(model: "go.probo.inc/probo/pkg/coredata.AssetType") { + PHYSICAL @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypePhysical") + VIRTUAL @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetTypeVirtual") } enum AssetOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.AssetOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldCreatedAt" - ) - AMOUNT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldAmount" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldCreatedAt") + AMOUNT @goEnum(value: "go.probo.inc/probo/pkg/coredata.AssetOrderFieldAmount") } enum DatumOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.DatumOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldCreatedAt" - ) - NAME - @goEnum(value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldName") + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldCreatedAt") + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldName") DATA_CLASSIFICATION @goEnum( value: "go.probo.inc/probo/pkg/coredata.DatumOrderFieldDataClassification" @@ -10788,59 +10632,38 @@ enum DatumOrderField enum DataClassification @goModel(model: "go.probo.inc/probo/pkg/coredata.DataClassification") { PUBLIC - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataClassificationPublic" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataClassificationPublic") INTERNAL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataClassificationInternal" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataClassificationInternal") CONFIDENTIAL @goEnum( value: "go.probo.inc/probo/pkg/coredata.DataClassificationConfidential" ) SECRET - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.DataClassificationSecret" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.DataClassificationSecret") } enum ControlStatus @goModel(model: "go.probo.inc/probo/pkg/coredata.ControlStatus") { INCLUDED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ControlStatusIncluded" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ControlStatusIncluded") EXCLUDED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ControlStatusExcluded" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ControlStatusExcluded") } enum AuditOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.AuditOrderField") { CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldCreatedAt" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldCreatedAt") VALID_FROM - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidFrom" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidFrom") VALID_UNTIL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidUntil" - ) - STATE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldState" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldValidUntil") + STATE @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldState") } enum NonconformityOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.NonconformityOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.NonconformityOrderField") { CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.NonconformityOrderFieldCreatedAt" @@ -10864,9 +10687,7 @@ enum NonconformityOrderField } enum ObligationOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ObligationOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.ObligationOrderField") { CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldCreatedAt" @@ -10880,9 +10701,7 @@ enum ObligationOrderField value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldDueDate" ) STATUS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldStatus" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.ObligationOrderFieldStatus") } enum ContinualImprovementOrderField @@ -10968,9 +10787,7 @@ enum TrustCenterReferenceOrderField } enum TrustCenterFileOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderField") { NAME @goEnum( value: "go.probo.inc/probo/pkg/coredata.TrustCenterFileOrderFieldName" @@ -10987,24 +10804,16 @@ enum TrustCenterFileOrderField enum SnapshotsType @goModel(model: "go.probo.inc/probo/pkg/coredata.SnapshotsType") { - RISKS - @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeRisks") - VENDORS - @goEnum( - 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") + RISKS @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeRisks") + VENDORS @goEnum(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 @goEnum( value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeNonconformities" ) OBLIGATIONS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeObligations" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeObligations") CONTINUAL_IMPROVEMENTS @goEnum( value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeContinualImprovements" @@ -11021,20 +10830,12 @@ enum SnapshotOrderField @goEnum( value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldCreatedAt" ) - NAME - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldName" - ) - TYPE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldType" - ) + NAME @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldName") + TYPE @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotOrderFieldType") } enum MembershipOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.MembershipOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.MembershipOrderField") { FULL_NAME @goEnum( value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldFullName" @@ -11044,9 +10845,7 @@ enum MembershipOrderField value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldEmailAddress" ) ROLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldRole" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldRole") CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.MembershipOrderFieldCreatedAt" @@ -11054,21 +10853,15 @@ enum MembershipOrderField } enum InvitationOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.InvitationOrderField" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.InvitationOrderField") { FULL_NAME @goEnum( value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldFullName" ) EMAIL - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldEmail" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldEmail") ROLE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldRole" - ) + @goEnum(value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldRole") CREATED_AT @goEnum( value: "go.probo.inc/probo/pkg/coredata.InvitationOrderFieldCreatedAt" @@ -12088,13 +11881,13 @@ type ProcessingActivity implements Node { purpose: String dataSubjectCategory: String personalDataCategory: String - specialOrCriminalData: ProcessingActivitySpecialOrCriminalData! + specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum! consentEvidenceLink: String lawfulBasis: ProcessingActivityLawfulBasis! recipients: String location: String internationalTransfers: Boolean! - transferSafeguards: ProcessingActivityTransferSafeguards + transferSafeguards: ProcessingActivityTransferSafeguard retentionPeriod: String securityMeasures: String dataProtectionImpactAssessment: ProcessingActivityDataProtectionImpactAssessment! @@ -13661,13 +13454,13 @@ input CreateProcessingActivityInput { purpose: String dataSubjectCategory: String personalDataCategory: String - specialOrCriminalData: ProcessingActivitySpecialOrCriminalData! + specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum! consentEvidenceLink: String lawfulBasis: ProcessingActivityLawfulBasis! recipients: String location: String internationalTransfers: Boolean! - transferSafeguards: ProcessingActivityTransferSafeguards + transferSafeguards: ProcessingActivityTransferSafeguard retentionPeriod: String securityMeasures: String dataProtectionImpactAssessment: ProcessingActivityDataProtectionImpactAssessment! @@ -13681,13 +13474,13 @@ input UpdateProcessingActivityInput { purpose: String @goField(omittable: true) dataSubjectCategory: String @goField(omittable: true) personalDataCategory: String @goField(omittable: true) - specialOrCriminalData: ProcessingActivitySpecialOrCriminalData + specialOrCriminalData: ProcessingActivitySpecialOrCriminalDatum consentEvidenceLink: String lawfulBasis: ProcessingActivityLawfulBasis recipients: String @goField(omittable: true) location: String @goField(omittable: true) internationalTransfers: Boolean - transferSafeguards: ProcessingActivityTransferSafeguards + transferSafeguards: ProcessingActivityTransferSafeguard @goField(omittable: true) retentionPeriod: String @goField(omittable: true) securityMeasures: String @goField(omittable: true) @@ -14530,9 +14323,7 @@ type DeleteSnapshotPayload { } enum SSLStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatus" - ) { + @goModel(model: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatus") { PENDING @goEnum( value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusPending" @@ -53421,9 +53212,9 @@ func (ec *executionContext) _ProcessingActivity_specialOrCriminalData(ctx contex } return graphql.Null } - res := resTmp.(coredata.ProcessingActivitySpecialOrCriminalData) + res := resTmp.(coredata.ProcessingActivitySpecialOrCriminalDatum) fc.Result = res - return ec.marshalNProcessingActivitySpecialOrCriminalData2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData(ctx, field.Selections, res) + return ec.marshalNProcessingActivitySpecialOrCriminalDatum2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_ProcessingActivity_specialOrCriminalData(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -53433,7 +53224,7 @@ func (ec *executionContext) fieldContext_ProcessingActivity_specialOrCriminalDat IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type ProcessingActivitySpecialOrCriminalData does not have child fields") + return nil, errors.New("field of type ProcessingActivitySpecialOrCriminalDatum does not have child fields") }, } return fc, nil @@ -53673,9 +53464,9 @@ func (ec *executionContext) _ProcessingActivity_transferSafeguards(ctx context.C if resTmp == nil { return graphql.Null } - res := resTmp.(*coredata.ProcessingActivityTransferSafeguards) + res := resTmp.(*coredata.ProcessingActivityTransferSafeguard) fc.Result = res - return ec.marshalOProcessingActivityTransferSafeguards2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguards(ctx, field.Selections, res) + return ec.marshalOProcessingActivityTransferSafeguard2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguard(ctx, field.Selections, res) } func (ec *executionContext) fieldContext_ProcessingActivity_transferSafeguards(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { @@ -53685,7 +53476,7 @@ func (ec *executionContext) fieldContext_ProcessingActivity_transferSafeguards(_ IsMethod: false, IsResolver: false, Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type ProcessingActivityTransferSafeguards does not have child fields") + return nil, errors.New("field of type ProcessingActivityTransferSafeguard does not have child fields") }, } return fc, nil @@ -74788,7 +74579,7 @@ func (ec *executionContext) unmarshalInputCreateProcessingActivityInput(ctx cont it.PersonalDataCategory = data case "specialOrCriminalData": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("specialOrCriminalData")) - data, err := ec.unmarshalNProcessingActivitySpecialOrCriminalData2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData(ctx, v) + data, err := ec.unmarshalNProcessingActivitySpecialOrCriminalDatum2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum(ctx, v) if err != nil { return it, err } @@ -74830,11 +74621,11 @@ func (ec *executionContext) unmarshalInputCreateProcessingActivityInput(ctx cont it.InternationalTransfers = data case "transferSafeguards": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("transferSafeguards")) - data, err := ec.unmarshalOProcessingActivityTransferSafeguards2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguards(ctx, v) + data, err := ec.unmarshalOProcessingActivityTransferSafeguard2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguard(ctx, v) if err != nil { return it, err } - it.TransferSafeguards = data + it.TransferSafeguard = data case "retentionPeriod": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("retentionPeriod")) data, err := ec.unmarshalOString2ᚖstring(ctx, v) @@ -79479,7 +79270,7 @@ func (ec *executionContext) unmarshalInputUpdateProcessingActivityInput(ctx cont it.PersonalDataCategory = graphql.OmittableOf(data) case "specialOrCriminalData": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("specialOrCriminalData")) - data, err := ec.unmarshalOProcessingActivitySpecialOrCriminalData2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData(ctx, v) + data, err := ec.unmarshalOProcessingActivitySpecialOrCriminalDatum2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum(ctx, v) if err != nil { return it, err } @@ -79521,7 +79312,7 @@ func (ec *executionContext) unmarshalInputUpdateProcessingActivityInput(ctx cont it.InternationalTransfers = data case "transferSafeguards": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("transferSafeguards")) - data, err := ec.unmarshalOProcessingActivityTransferSafeguards2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguards(ctx, v) + data, err := ec.unmarshalOProcessingActivityTransferSafeguard2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguard(ctx, v) if err != nil { return it, err } @@ -104538,15 +104329,15 @@ var ( } ) -func (ec *executionContext) unmarshalNProcessingActivitySpecialOrCriminalData2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData(ctx context.Context, v any) (coredata.ProcessingActivitySpecialOrCriminalData, error) { +func (ec *executionContext) unmarshalNProcessingActivitySpecialOrCriminalDatum2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum(ctx context.Context, v any) (coredata.ProcessingActivitySpecialOrCriminalDatum, error) { tmp, err := graphql.UnmarshalString(v) - res := unmarshalNProcessingActivitySpecialOrCriminalData2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData[tmp] + res := unmarshalNProcessingActivitySpecialOrCriminalDatum2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum[tmp] return res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalNProcessingActivitySpecialOrCriminalData2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData(ctx context.Context, sel ast.SelectionSet, v coredata.ProcessingActivitySpecialOrCriminalData) graphql.Marshaler { +func (ec *executionContext) marshalNProcessingActivitySpecialOrCriminalDatum2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum(ctx context.Context, sel ast.SelectionSet, v coredata.ProcessingActivitySpecialOrCriminalDatum) graphql.Marshaler { _ = sel - res := graphql.MarshalString(marshalNProcessingActivitySpecialOrCriminalData2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData[v]) + res := graphql.MarshalString(marshalNProcessingActivitySpecialOrCriminalDatum2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum[v]) if res == graphql.Null { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "the requested element is null which the schema does not allow") @@ -104556,15 +104347,15 @@ func (ec *executionContext) marshalNProcessingActivitySpecialOrCriminalData2go } var ( - unmarshalNProcessingActivitySpecialOrCriminalData2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData = map[string]coredata.ProcessingActivitySpecialOrCriminalData{ - "YES": coredata.ProcessingActivitySpecialOrCriminalDataYes, - "NO": coredata.ProcessingActivitySpecialOrCriminalDataNo, - "POSSIBLE": coredata.ProcessingActivitySpecialOrCriminalDataPossible, + unmarshalNProcessingActivitySpecialOrCriminalDatum2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum = map[string]coredata.ProcessingActivitySpecialOrCriminalDatum{ + "YES": coredata.ProcessingActivitySpecialOrCriminalDatumYes, + "NO": coredata.ProcessingActivitySpecialOrCriminalDatumNo, + "POSSIBLE": coredata.ProcessingActivitySpecialOrCriminalDatumPossible, } - marshalNProcessingActivitySpecialOrCriminalData2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData = map[coredata.ProcessingActivitySpecialOrCriminalData]string{ - coredata.ProcessingActivitySpecialOrCriminalDataYes: "YES", - coredata.ProcessingActivitySpecialOrCriminalDataNo: "NO", - coredata.ProcessingActivitySpecialOrCriminalDataPossible: "POSSIBLE", + marshalNProcessingActivitySpecialOrCriminalDatum2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum = map[coredata.ProcessingActivitySpecialOrCriminalDatum]string{ + coredata.ProcessingActivitySpecialOrCriminalDatumYes: "YES", + coredata.ProcessingActivitySpecialOrCriminalDatumNo: "NO", + coredata.ProcessingActivitySpecialOrCriminalDatumPossible: "POSSIBLE", } ) @@ -109222,35 +109013,35 @@ func (ec *executionContext) unmarshalOProcessingActivityOrder2ᚖgoᚗproboᚗin return &res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) unmarshalOProcessingActivitySpecialOrCriminalData2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData(ctx context.Context, v any) (*coredata.ProcessingActivitySpecialOrCriminalData, error) { +func (ec *executionContext) unmarshalOProcessingActivitySpecialOrCriminalDatum2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum(ctx context.Context, v any) (*coredata.ProcessingActivitySpecialOrCriminalDatum, error) { if v == nil { return nil, nil } tmp, err := graphql.UnmarshalString(v) - res := unmarshalOProcessingActivitySpecialOrCriminalData2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData[tmp] + res := unmarshalOProcessingActivitySpecialOrCriminalDatum2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum[tmp] return &res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalOProcessingActivitySpecialOrCriminalData2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData(ctx context.Context, sel ast.SelectionSet, v *coredata.ProcessingActivitySpecialOrCriminalData) graphql.Marshaler { +func (ec *executionContext) marshalOProcessingActivitySpecialOrCriminalDatum2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum(ctx context.Context, sel ast.SelectionSet, v *coredata.ProcessingActivitySpecialOrCriminalDatum) graphql.Marshaler { if v == nil { return graphql.Null } _ = sel _ = ctx - res := graphql.MarshalString(marshalOProcessingActivitySpecialOrCriminalData2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData[*v]) + res := graphql.MarshalString(marshalOProcessingActivitySpecialOrCriminalDatum2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum[*v]) return res } var ( - unmarshalOProcessingActivitySpecialOrCriminalData2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData = map[string]coredata.ProcessingActivitySpecialOrCriminalData{ - "YES": coredata.ProcessingActivitySpecialOrCriminalDataYes, - "NO": coredata.ProcessingActivitySpecialOrCriminalDataNo, - "POSSIBLE": coredata.ProcessingActivitySpecialOrCriminalDataPossible, + unmarshalOProcessingActivitySpecialOrCriminalDatum2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum = map[string]coredata.ProcessingActivitySpecialOrCriminalDatum{ + "YES": coredata.ProcessingActivitySpecialOrCriminalDatumYes, + "NO": coredata.ProcessingActivitySpecialOrCriminalDatumNo, + "POSSIBLE": coredata.ProcessingActivitySpecialOrCriminalDatumPossible, } - marshalOProcessingActivitySpecialOrCriminalData2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalData = map[coredata.ProcessingActivitySpecialOrCriminalData]string{ - coredata.ProcessingActivitySpecialOrCriminalDataYes: "YES", - coredata.ProcessingActivitySpecialOrCriminalDataNo: "NO", - coredata.ProcessingActivitySpecialOrCriminalDataPossible: "POSSIBLE", + marshalOProcessingActivitySpecialOrCriminalDatum2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivitySpecialOrCriminalDatum = map[coredata.ProcessingActivitySpecialOrCriminalDatum]string{ + coredata.ProcessingActivitySpecialOrCriminalDatumYes: "YES", + coredata.ProcessingActivitySpecialOrCriminalDatumNo: "NO", + coredata.ProcessingActivitySpecialOrCriminalDatumPossible: "POSSIBLE", } ) @@ -109284,41 +109075,41 @@ var ( } ) -func (ec *executionContext) unmarshalOProcessingActivityTransferSafeguards2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguards(ctx context.Context, v any) (*coredata.ProcessingActivityTransferSafeguards, error) { +func (ec *executionContext) unmarshalOProcessingActivityTransferSafeguard2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguard(ctx context.Context, v any) (*coredata.ProcessingActivityTransferSafeguard, error) { if v == nil { return nil, nil } tmp, err := graphql.UnmarshalString(v) - res := unmarshalOProcessingActivityTransferSafeguards2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguards[tmp] + res := unmarshalOProcessingActivityTransferSafeguard2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguard[tmp] return &res, graphql.ErrorOnPath(ctx, err) } -func (ec *executionContext) marshalOProcessingActivityTransferSafeguards2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguards(ctx context.Context, sel ast.SelectionSet, v *coredata.ProcessingActivityTransferSafeguards) graphql.Marshaler { +func (ec *executionContext) marshalOProcessingActivityTransferSafeguard2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguard(ctx context.Context, sel ast.SelectionSet, v *coredata.ProcessingActivityTransferSafeguard) graphql.Marshaler { if v == nil { return graphql.Null } _ = sel _ = ctx - res := graphql.MarshalString(marshalOProcessingActivityTransferSafeguards2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguards[*v]) + res := graphql.MarshalString(marshalOProcessingActivityTransferSafeguard2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguard[*v]) return res } var ( - unmarshalOProcessingActivityTransferSafeguards2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguards = map[string]coredata.ProcessingActivityTransferSafeguards{ - "STANDARD_CONTRACTUAL_CLAUSES": coredata.ProcessingActivityTransferSafeguardsStandardContractualClauses, - "BINDING_CORPORATE_RULES": coredata.ProcessingActivityTransferSafeguardsBindingCorporateRules, - "ADEQUACY_DECISION": coredata.ProcessingActivityTransferSafeguardsAdequacyDecision, - "DEROGATIONS": coredata.ProcessingActivityTransferSafeguardsDerogations, - "CODES_OF_CONDUCT": coredata.ProcessingActivityTransferSafeguardsCodesOfConduct, - "CERTIFICATION_MECHANISMS": coredata.ProcessingActivityTransferSafeguardsCertificationMechanisms, + unmarshalOProcessingActivityTransferSafeguard2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguard = map[string]coredata.ProcessingActivityTransferSafeguard{ + "STANDARD_CONTRACTUAL_CLAUSES": coredata.ProcessingActivityTransferSafeguardStandardContractualClauses, + "BINDING_CORPORATE_RULES": coredata.ProcessingActivityTransferSafeguardBindingCorporateRules, + "ADEQUACY_DECISION": coredata.ProcessingActivityTransferSafeguardAdequacyDecision, + "DEROGATIONS": coredata.ProcessingActivityTransferSafeguardDerogations, + "CODES_OF_CONDUCT": coredata.ProcessingActivityTransferSafeguardCodesOfConduct, + "CERTIFICATION_MECHANISMS": coredata.ProcessingActivityTransferSafeguardCertificationMechanisms, } - marshalOProcessingActivityTransferSafeguards2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguards = map[coredata.ProcessingActivityTransferSafeguards]string{ - coredata.ProcessingActivityTransferSafeguardsStandardContractualClauses: "STANDARD_CONTRACTUAL_CLAUSES", - coredata.ProcessingActivityTransferSafeguardsBindingCorporateRules: "BINDING_CORPORATE_RULES", - coredata.ProcessingActivityTransferSafeguardsAdequacyDecision: "ADEQUACY_DECISION", - coredata.ProcessingActivityTransferSafeguardsDerogations: "DEROGATIONS", - coredata.ProcessingActivityTransferSafeguardsCodesOfConduct: "CODES_OF_CONDUCT", - coredata.ProcessingActivityTransferSafeguardsCertificationMechanisms: "CERTIFICATION_MECHANISMS", + marshalOProcessingActivityTransferSafeguard2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐProcessingActivityTransferSafeguard = map[coredata.ProcessingActivityTransferSafeguard]string{ + coredata.ProcessingActivityTransferSafeguardStandardContractualClauses: "STANDARD_CONTRACTUAL_CLAUSES", + coredata.ProcessingActivityTransferSafeguardBindingCorporateRules: "BINDING_CORPORATE_RULES", + coredata.ProcessingActivityTransferSafeguardAdequacyDecision: "ADEQUACY_DECISION", + coredata.ProcessingActivityTransferSafeguardDerogations: "DEROGATIONS", + coredata.ProcessingActivityTransferSafeguardCodesOfConduct: "CODES_OF_CONDUCT", + coredata.ProcessingActivityTransferSafeguardCertificationMechanisms: "CERTIFICATION_MECHANISMS", } ) diff --git a/pkg/server/api/console/v1/types/processing_activity.go b/pkg/server/api/console/v1/types/processing_activity.go index b53a76d3c..e4cbfdc5f 100644 --- a/pkg/server/api/console/v1/types/processing_activity.go +++ b/pkg/server/api/console/v1/types/processing_activity.go @@ -69,7 +69,7 @@ func NewProcessingActivity(par *coredata.ProcessingActivity) *ProcessingActivity Recipients: par.Recipients, Location: par.Location, InternationalTransfers: par.InternationalTransfers, - TransferSafeguards: par.TransferSafeguards, + TransferSafeguards: par.TransferSafeguard, RetentionPeriod: par.RetentionPeriod, SecurityMeasures: par.SecurityMeasures, DataProtectionImpactAssessment: par.DataProtectionImpactAssessment, diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index b5d4c8f2a..69ac74d6d 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -440,13 +440,13 @@ type CreateProcessingActivityInput struct { Purpose *string `json:"purpose,omitempty"` DataSubjectCategory *string `json:"dataSubjectCategory,omitempty"` PersonalDataCategory *string `json:"personalDataCategory,omitempty"` - SpecialOrCriminalData coredata.ProcessingActivitySpecialOrCriminalData `json:"specialOrCriminalData"` + SpecialOrCriminalData coredata.ProcessingActivitySpecialOrCriminalDatum `json:"specialOrCriminalData"` ConsentEvidenceLink *string `json:"consentEvidenceLink,omitempty"` LawfulBasis coredata.ProcessingActivityLawfulBasis `json:"lawfulBasis"` Recipients *string `json:"recipients,omitempty"` Location *string `json:"location,omitempty"` InternationalTransfers bool `json:"internationalTransfers"` - TransferSafeguards *coredata.ProcessingActivityTransferSafeguards `json:"transferSafeguards,omitempty"` + TransferSafeguard *coredata.ProcessingActivityTransferSafeguard `json:"transferSafeguards,omitempty"` RetentionPeriod *string `json:"retentionPeriod,omitempty"` SecurityMeasures *string `json:"securityMeasures,omitempty"` DataProtectionImpactAssessment coredata.ProcessingActivityDataProtectionImpactAssessment `json:"dataProtectionImpactAssessment"` @@ -1518,13 +1518,13 @@ type ProcessingActivity struct { Purpose *string `json:"purpose,omitempty"` DataSubjectCategory *string `json:"dataSubjectCategory,omitempty"` PersonalDataCategory *string `json:"personalDataCategory,omitempty"` - SpecialOrCriminalData coredata.ProcessingActivitySpecialOrCriminalData `json:"specialOrCriminalData"` + SpecialOrCriminalData coredata.ProcessingActivitySpecialOrCriminalDatum `json:"specialOrCriminalData"` ConsentEvidenceLink *string `json:"consentEvidenceLink,omitempty"` LawfulBasis coredata.ProcessingActivityLawfulBasis `json:"lawfulBasis"` Recipients *string `json:"recipients,omitempty"` Location *string `json:"location,omitempty"` InternationalTransfers bool `json:"internationalTransfers"` - TransferSafeguards *coredata.ProcessingActivityTransferSafeguards `json:"transferSafeguards,omitempty"` + TransferSafeguards *coredata.ProcessingActivityTransferSafeguard `json:"transferSafeguards,omitempty"` RetentionPeriod *string `json:"retentionPeriod,omitempty"` SecurityMeasures *string `json:"securityMeasures,omitempty"` DataProtectionImpactAssessment coredata.ProcessingActivityDataProtectionImpactAssessment `json:"dataProtectionImpactAssessment"` @@ -2022,23 +2022,23 @@ type UpdatePeoplePayload struct { } type UpdateProcessingActivityInput struct { - ID gid.GID `json:"id"` - Name *string `json:"name,omitempty"` - Purpose graphql.Omittable[*string] `json:"purpose,omitempty"` - DataSubjectCategory graphql.Omittable[*string] `json:"dataSubjectCategory,omitempty"` - PersonalDataCategory graphql.Omittable[*string] `json:"personalDataCategory,omitempty"` - SpecialOrCriminalData *coredata.ProcessingActivitySpecialOrCriminalData `json:"specialOrCriminalData,omitempty"` - ConsentEvidenceLink *string `json:"consentEvidenceLink,omitempty"` - LawfulBasis *coredata.ProcessingActivityLawfulBasis `json:"lawfulBasis,omitempty"` - Recipients graphql.Omittable[*string] `json:"recipients,omitempty"` - Location graphql.Omittable[*string] `json:"location,omitempty"` - InternationalTransfers *bool `json:"internationalTransfers,omitempty"` - TransferSafeguards graphql.Omittable[*coredata.ProcessingActivityTransferSafeguards] `json:"transferSafeguards,omitempty"` - RetentionPeriod graphql.Omittable[*string] `json:"retentionPeriod,omitempty"` - SecurityMeasures graphql.Omittable[*string] `json:"securityMeasures,omitempty"` - DataProtectionImpactAssessment *coredata.ProcessingActivityDataProtectionImpactAssessment `json:"dataProtectionImpactAssessment,omitempty"` - TransferImpactAssessment *coredata.ProcessingActivityTransferImpactAssessment `json:"transferImpactAssessment,omitempty"` - VendorIds []gid.GID `json:"vendorIds,omitempty"` + ID gid.GID `json:"id"` + Name *string `json:"name,omitempty"` + Purpose graphql.Omittable[*string] `json:"purpose,omitempty"` + DataSubjectCategory graphql.Omittable[*string] `json:"dataSubjectCategory,omitempty"` + PersonalDataCategory graphql.Omittable[*string] `json:"personalDataCategory,omitempty"` + SpecialOrCriminalData *coredata.ProcessingActivitySpecialOrCriminalDatum `json:"specialOrCriminalData,omitempty"` + ConsentEvidenceLink *string `json:"consentEvidenceLink,omitempty"` + LawfulBasis *coredata.ProcessingActivityLawfulBasis `json:"lawfulBasis,omitempty"` + Recipients graphql.Omittable[*string] `json:"recipients,omitempty"` + Location graphql.Omittable[*string] `json:"location,omitempty"` + InternationalTransfers *bool `json:"internationalTransfers,omitempty"` + TransferSafeguards graphql.Omittable[*coredata.ProcessingActivityTransferSafeguard] `json:"transferSafeguards,omitempty"` + RetentionPeriod graphql.Omittable[*string] `json:"retentionPeriod,omitempty"` + SecurityMeasures graphql.Omittable[*string] `json:"securityMeasures,omitempty"` + DataProtectionImpactAssessment *coredata.ProcessingActivityDataProtectionImpactAssessment `json:"dataProtectionImpactAssessment,omitempty"` + TransferImpactAssessment *coredata.ProcessingActivityTransferImpactAssessment `json:"transferImpactAssessment,omitempty"` + VendorIds []gid.GID `json:"vendorIds,omitempty"` } type UpdateProcessingActivityPayload struct { diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index 0ba58c57d..8cad9c6df 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -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) { prb := r.ProboService(ctx, input.TrustCenterID.TenantID()) - trustCenter, file, err := prb.TrustCenters.DeleteNDA(ctx, &probo.DeleteTrustCenterNDARequest{ - TrustCenterID: input.TrustCenterID, - }) + trustCenter, file, err := prb.TrustCenters.DeleteNDA(ctx, input.TrustCenterID) if err != nil { 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) { prb := r.ProboService(ctx, input.ID.TenantID()) - err := prb.TrustCenterAccesses.Delete(ctx, &probo.DeleteTrustCenterAccessRequest{ - ID: input.ID, - }) + err := prb.TrustCenterAccesses.Delete(ctx, input.ID) if err != nil { 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) { prb := r.ProboService(ctx, input.ID.TenantID()) - err := prb.TrustCenterReferences.Delete(ctx, &probo.DeleteTrustCenterReferenceRequest{ - ID: input.ID, - }) + err := prb.TrustCenterReferences.Delete(ctx, input.ID) if err != nil { 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) { prb := r.ProboService(ctx, input.ID.TenantID()) - err := prb.TrustCenterFiles.Delete(ctx, &probo.DeleteTrustCenterFileRequest{ - ID: input.ID, - }) + err := prb.TrustCenterFiles.Delete(ctx, input.ID) if err != nil { 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 { var errNoChanges *coredata.ErrDocumentVersionNoChanges 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)) } @@ -3568,7 +3560,7 @@ func (r *mutationResolver) CreateProcessingActivity(ctx context.Context, input t Recipients: input.Recipients, Location: input.Location, InternationalTransfers: input.InternationalTransfers, - TransferSafeguards: input.TransferSafeguards, + TransferSafeguard: input.TransferSafeguard, RetentionPeriod: input.RetentionPeriod, SecurityMeasures: input.SecurityMeasures, DataProtectionImpactAssessment: input.DataProtectionImpactAssessment, @@ -3601,7 +3593,7 @@ func (r *mutationResolver) UpdateProcessingActivity(ctx context.Context, input t Recipients: UnwrapOmittable(input.Recipients), Location: UnwrapOmittable(input.Location), InternationalTransfers: input.InternationalTransfers, - TransferSafeguards: UnwrapOmittable(input.TransferSafeguards), + TransferSafeguard: UnwrapOmittable(input.TransferSafeguards), RetentionPeriod: UnwrapOmittable(input.RetentionPeriod), SecurityMeasures: UnwrapOmittable(input.SecurityMeasures), DataProtectionImpactAssessment: input.DataProtectionImpactAssessment, diff --git a/pkg/server/gqlutils/errors.go b/pkg/server/gqlutils/errors.go index 2ee3781b4..b8cfb8397 100644 --- a/pkg/server/gqlutils/errors.go +++ b/pkg/server/gqlutils/errors.go @@ -30,9 +30,7 @@ func Unauthorized() *gqlerror.Error { } func AuthenticationRequired(details map[string]any) *gqlerror.Error { - extensions := map[string]any{ - "code": "AUTHENTICATION_REQUIRED", - } + extensions := map[string]any{"code": "AUTHENTICATION_REQUIRED"} maps.Copy(extensions, details) 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{ - Message: err.Error(), - Extensions: map[string]any{ - "code": "INVALID", - }, + Message: err.Error(), + Extensions: extensions, } } diff --git a/pkg/server/gqlutils/recovery.go b/pkg/server/gqlutils/recovery.go index 7e6f5fadb..bf87bc0e4 100644 --- a/pkg/server/gqlutils/recovery.go +++ b/pkg/server/gqlutils/recovery.go @@ -24,6 +24,7 @@ import ( "go.gearno.de/kit/log" "go.probo.inc/probo/pkg/auth" "go.probo.inc/probo/pkg/authz" + "go.probo.inc/probo/pkg/validator" ) 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 if errTyped, ok := err.(error); ok && errors.As(errTyped, &tenantAccessErr) { return Unauthorized() @@ -65,5 +87,6 @@ func asError(err any) error { if e, ok := err.(error); ok { return e } + return errors.New("unknown panic") } diff --git a/pkg/validator/checkeach_slice_test.go b/pkg/validator/checkeach_slice_test.go new file mode 100644 index 000000000..8f0588a5c --- /dev/null +++ b/pkg/validator/checkeach_slice_test.go @@ -0,0 +1,201 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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) + } +} diff --git a/pkg/validator/double_pointer_test.go b/pkg/validator/double_pointer_test.go new file mode 100644 index 000000000..27d94a1da --- /dev/null +++ b/pkg/validator/double_pointer_test.go @@ -0,0 +1,98 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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()) + } + }) +} diff --git a/pkg/validator/errors.go b/pkg/validator/errors.go new file mode 100644 index 000000000..88704db32 --- /dev/null +++ b/pkg/validator/errors.go @@ -0,0 +1,107 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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, + } +} diff --git a/pkg/validator/oneof_custom_type_test.go b/pkg/validator/oneof_custom_type_test.go new file mode 100644 index 000000000..0f220272b --- /dev/null +++ b/pkg/validator/oneof_custom_type_test.go @@ -0,0 +1,82 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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()) + } + } + }) + } +} diff --git a/pkg/validator/optional_pointer_test.go b/pkg/validator/optional_pointer_test.go new file mode 100644 index 000000000..15ed9ce4f --- /dev/null +++ b/pkg/validator/optional_pointer_test.go @@ -0,0 +1,116 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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()) + } + } + }) + } +} diff --git a/pkg/validator/validation.go b/pkg/validator/validation.go new file mode 100644 index 000000000..825f9ba0d --- /dev/null +++ b/pkg/validator/validation.go @@ -0,0 +1,149 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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 +} diff --git a/pkg/validator/validation_bench_test.go b/pkg/validator/validation_bench_test.go new file mode 100644 index 000000000..9f88e221e --- /dev/null +++ b/pkg/validator/validation_bench_test.go @@ -0,0 +1,425 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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) + } +} diff --git a/pkg/validator/validation_test.go b/pkg/validator/validation_test.go new file mode 100644 index 000000000..2f2fc13c5 --- /dev/null +++ b/pkg/validator/validation_test.go @@ -0,0 +1,451 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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") + } + }) +} diff --git a/pkg/validator/validator_collection.go b/pkg/validator/validator_collection.go new file mode 100644 index 000000000..88ef77465 --- /dev/null +++ b/pkg/validator/validator_collection.go @@ -0,0 +1,109 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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 + } +} diff --git a/pkg/validator/validator_collection_test.go b/pkg/validator/validator_collection_test.go new file mode 100644 index 000000000..08befba3a --- /dev/null +++ b/pkg/validator/validator_collection_test.go @@ -0,0 +1,220 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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) + } + }) +} diff --git a/pkg/validator/validator_common.go b/pkg/validator/validator_common.go new file mode 100644 index 000000000..8d3eb2bdc --- /dev/null +++ b/pkg/validator/validator_common.go @@ -0,0 +1,71 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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 + } +} diff --git a/pkg/validator/validator_common_test.go b/pkg/validator/validator_common_test.go new file mode 100644 index 000000000..9d367a7be --- /dev/null +++ b/pkg/validator/validator_common_test.go @@ -0,0 +1,270 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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) + } + }) +} diff --git a/pkg/validator/validator_conditional.go b/pkg/validator/validator_conditional.go new file mode 100644 index 000000000..3d6d70efc --- /dev/null +++ b/pkg/validator/validator_conditional.go @@ -0,0 +1,71 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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) +} diff --git a/pkg/validator/validator_conditional_test.go b/pkg/validator/validator_conditional_test.go new file mode 100644 index 000000000..fe4f274b7 --- /dev/null +++ b/pkg/validator/validator_conditional_test.go @@ -0,0 +1,182 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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") + } + }) +} diff --git a/pkg/validator/validator_custom.go b/pkg/validator/validator_custom.go new file mode 100644 index 000000000..fe8451a0f --- /dev/null +++ b/pkg/validator/validator_custom.go @@ -0,0 +1,26 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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 + } +} diff --git a/pkg/validator/validator_custom_test.go b/pkg/validator/validator_custom_test.go new file mode 100644 index 000000000..5cca4e9db --- /dev/null +++ b/pkg/validator/validator_custom_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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) + } + }) + } +} diff --git a/pkg/validator/validator_format.go b/pkg/validator/validator_format.go new file mode 100644 index 000000000..6c7b49a85 --- /dev/null +++ b/pkg/validator/validator_format.go @@ -0,0 +1,255 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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 + } +} diff --git a/pkg/validator/validator_format_test.go b/pkg/validator/validator_format_test.go new file mode 100644 index 000000000..7aa17c4f7 --- /dev/null +++ b/pkg/validator/validator_format_test.go @@ -0,0 +1,432 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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) + } + }) +} diff --git a/pkg/validator/validator_numeric.go b/pkg/validator/validator_numeric.go new file mode 100644 index 000000000..ed90dbd8e --- /dev/null +++ b/pkg/validator/validator_numeric.go @@ -0,0 +1,203 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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 + } +} diff --git a/pkg/validator/validator_numeric_test.go b/pkg/validator/validator_numeric_test.go new file mode 100644 index 000000000..a511f83ee --- /dev/null +++ b/pkg/validator/validator_numeric_test.go @@ -0,0 +1,95 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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) + } + }) + } +} diff --git a/pkg/validator/validator_security.go b/pkg/validator/validator_security.go new file mode 100644 index 000000000..b0375676b --- /dev/null +++ b/pkg/validator/validator_security.go @@ -0,0 +1,173 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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., " + 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 World" + 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 := "
Content
" + 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
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 := `` + err := NoHTML()(&str) + if err == nil { + t.Error("expected validation error for img tag") + } + }) + + t.Run("invalid - anchor tag", func(t *testing.T) { + str := `Click` + 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 . +// +// 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 + } +} diff --git a/pkg/validator/validator_string_test.go b/pkg/validator/validator_string_test.go new file mode 100644 index 000000000..234461cef --- /dev/null +++ b/pkg/validator/validator_string_test.go @@ -0,0 +1,303 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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) + } + }) + } +} diff --git a/pkg/validator/validator_time.go b/pkg/validator/validator_time.go new file mode 100644 index 000000000..55f45d461 --- /dev/null +++ b/pkg/validator/validator_time.go @@ -0,0 +1,208 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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" + "time" +) + +// After validates that a time is after the specified reference time. +// The reference time can be either time.Time or *time.Time. +func After(t any) ValidatorFunc { + return func(value any) *ValidationError { + // Extract the reference time + refValue, refIsNil := dereferenceValue(t) + if refIsNil { + return nil // No reference time to compare against + } + + refTime, ok := refValue.(time.Time) + if !ok { + return newValidationError(ErrorCodeInvalidFormat, "reference time must be time.Time") + } + + // Extract the value being validated + actualValue, isNil := dereferenceValue(value) + if isNil { + return nil + } + + timeVal, ok := actualValue.(time.Time) + if !ok { + return newValidationError(ErrorCodeInvalidFormat, "value must be a time.Time") + } + + if !timeVal.After(refTime) { + return newValidationError( + ErrorCodeOutOfRange, + fmt.Sprintf("must be after %s", refTime.Format(time.RFC3339)), + ) + } + + return nil + } +} + +// Before validates that a time is before the specified reference time. +// The reference time can be either time.Time or *time.Time. +func Before(t any) ValidatorFunc { + return func(value any) *ValidationError { + // Extract the reference time + refValue, refIsNil := dereferenceValue(t) + if refIsNil { + return nil // No reference time to compare against + } + + refTime, ok := refValue.(time.Time) + if !ok { + return newValidationError(ErrorCodeInvalidFormat, "reference time must be time.Time") + } + + // Extract the value being validated + actualValue, isNil := dereferenceValue(value) + if isNil { + return nil + } + + timeVal, ok := actualValue.(time.Time) + if !ok { + return newValidationError(ErrorCodeInvalidFormat, "value must be a time.Time") + } + + if !timeVal.Before(refTime) { + return newValidationError( + ErrorCodeOutOfRange, + fmt.Sprintf("must be before %s", refTime.Format(time.RFC3339)), + ) + } + + return nil + } +} + +// FutureDate validates that a time is in the future. +func FutureDate() ValidatorFunc { + return func(value any) *ValidationError { + actualValue, isNil := dereferenceValue(value) + if isNil { + return nil + } + + timeVal, ok := actualValue.(time.Time) + if !ok { + return newValidationError(ErrorCodeInvalidFormat, "value must be a time.Time") + } + + if !timeVal.After(time.Now()) { + return newValidationError(ErrorCodeOutOfRange, "must be a future date") + } + + return nil + } +} + +// PastDate validates that a time is in the past. +func PastDate() ValidatorFunc { + return func(value any) *ValidationError { + actualValue, isNil := dereferenceValue(value) + if isNil { + return nil + } + + timeVal, ok := actualValue.(time.Time) + if !ok { + return newValidationError(ErrorCodeInvalidFormat, "value must be a time.Time") + } + + if !timeVal.Before(time.Now()) { + return newValidationError(ErrorCodeOutOfRange, "must be a past date") + } + + return nil + } +} + +// MinDuration validates that a duration is at least the specified minimum value. +func MinDuration(min time.Duration) ValidatorFunc { + return func(value any) *ValidationError { + actualValue, isNil := dereferenceValue(value) + if isNil { + return nil + } + + duration, ok := actualValue.(time.Duration) + if !ok { + return newValidationError(ErrorCodeInvalidFormat, "value must be a time.Duration") + } + + if duration < min { + return newValidationError( + ErrorCodeOutOfRange, + fmt.Sprintf("must be at least %s", min), + ) + } + + return nil + } +} + +// MaxDuration validates that a duration does not exceed the specified maximum value. +func MaxDuration(max time.Duration) ValidatorFunc { + return func(value any) *ValidationError { + actualValue, isNil := dereferenceValue(value) + if isNil { + return nil + } + + duration, ok := actualValue.(time.Duration) + if !ok { + return newValidationError(ErrorCodeInvalidFormat, "value must be a time.Duration") + } + + if duration > max { + return newValidationError( + ErrorCodeOutOfRange, + fmt.Sprintf("must be at most %s", max), + ) + } + + return nil + } +} + +// RangeDuration validates that a duration is within the specified range (inclusive). +func RangeDuration(min, max time.Duration) ValidatorFunc { + return func(value any) *ValidationError { + actualValue, isNil := dereferenceValue(value) + if isNil { + return nil + } + + duration, ok := actualValue.(time.Duration) + if !ok { + return newValidationError(ErrorCodeInvalidFormat, "value must be a time.Duration") + } + + if duration < min || duration > max { + return newValidationError( + ErrorCodeOutOfRange, + fmt.Sprintf("must be between %s and %s", min, max), + ) + } + + return nil + } +} diff --git a/pkg/validator/validator_time_test.go b/pkg/validator/validator_time_test.go new file mode 100644 index 000000000..fc27f3a02 --- /dev/null +++ b/pkg/validator/validator_time_test.go @@ -0,0 +1,293 @@ +// Copyright (c) 2025 Probo Inc . +// +// 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 TestAfter(t *testing.T) { + now := time.Now() + past := now.Add(-24 * time.Hour) + future := now.Add(24 * time.Hour) + + t.Run("time after reference", func(t *testing.T) { + err := After(past)(&future) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + }) + + t.Run("time before reference", func(t *testing.T) { + err := After(future)(&past) + if err == nil { + t.Fatal("expected validation error") + } + if err.Code != ErrorCodeOutOfRange { + t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code) + } + }) + + t.Run("same time", func(t *testing.T) { + err := After(now)(&now) + if err == nil { + t.Error("expected validation error for equal times") + } + }) + + t.Run("nil pointer", func(t *testing.T) { + var timeVal *time.Time + err := After(now)(timeVal) + if err != nil { + t.Errorf("expected no error for nil, got: %v", err) + } + }) +} + +func TestBefore(t *testing.T) { + now := time.Now() + past := now.Add(-24 * time.Hour) + future := now.Add(24 * time.Hour) + + t.Run("time before reference", func(t *testing.T) { + err := Before(future)(&past) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + }) + + t.Run("time after reference", func(t *testing.T) { + err := Before(past)(&future) + if err == nil { + t.Fatal("expected validation error") + } + if err.Code != ErrorCodeOutOfRange { + t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code) + } + }) + + t.Run("same time", func(t *testing.T) { + err := Before(now)(&now) + if err == nil { + t.Error("expected validation error for equal times") + } + }) + + t.Run("nil pointer", func(t *testing.T) { + var timeVal *time.Time + err := Before(now)(timeVal) + if err != nil { + t.Errorf("expected no error for nil, got: %v", err) + } + }) +} + +func TestFutureDate(t *testing.T) { + future := time.Now().Add(24 * time.Hour) + past := time.Now().Add(-24 * time.Hour) + + t.Run("future date", func(t *testing.T) { + err := FutureDate()(&future) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + }) + + t.Run("past date", func(t *testing.T) { + err := FutureDate()(&past) + if err == nil { + t.Fatal("expected validation error") + } + if err.Code != ErrorCodeOutOfRange { + t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code) + } + }) + + t.Run("nil pointer", func(t *testing.T) { + var timeVal *time.Time + err := FutureDate()(timeVal) + if err != nil { + t.Errorf("expected no error for nil, got: %v", err) + } + }) +} + +func TestPastDate(t *testing.T) { + future := time.Now().Add(24 * time.Hour) + past := time.Now().Add(-24 * time.Hour) + + t.Run("past date", func(t *testing.T) { + err := PastDate()(&past) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + }) + + t.Run("future date", func(t *testing.T) { + err := PastDate()(&future) + if err == nil { + t.Fatal("expected validation error") + } + if err.Code != ErrorCodeOutOfRange { + t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code) + } + }) + + t.Run("nil pointer", func(t *testing.T) { + var timeVal *time.Time + err := PastDate()(timeVal) + if err != nil { + t.Errorf("expected no error for nil, got: %v", err) + } + }) +} + +func TestMinDuration(t *testing.T) { + minDuration := 10 * time.Minute + + t.Run("duration above minimum", func(t *testing.T) { + duration := 20 * time.Minute + err := MinDuration(minDuration)(&duration) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + }) + + t.Run("duration equal to minimum", func(t *testing.T) { + duration := 10 * time.Minute + err := MinDuration(minDuration)(&duration) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + }) + + t.Run("duration below minimum", func(t *testing.T) { + duration := 5 * time.Minute + err := MinDuration(minDuration)(&duration) + if err == nil { + t.Fatal("expected validation error") + } + if err.Code != ErrorCodeOutOfRange { + t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code) + } + }) + + t.Run("nil pointer", func(t *testing.T) { + var duration *time.Duration + err := MinDuration(minDuration)(duration) + if err != nil { + t.Errorf("expected no error for nil, got: %v", err) + } + }) +} + +func TestMaxDuration(t *testing.T) { + maxDuration := 1 * time.Hour + + t.Run("duration below maximum", func(t *testing.T) { + duration := 30 * time.Minute + err := MaxDuration(maxDuration)(&duration) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + }) + + t.Run("duration equal to maximum", func(t *testing.T) { + duration := 1 * time.Hour + err := MaxDuration(maxDuration)(&duration) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + }) + + t.Run("duration above maximum", func(t *testing.T) { + duration := 2 * time.Hour + err := MaxDuration(maxDuration)(&duration) + if err == nil { + t.Fatal("expected validation error") + } + if err.Code != ErrorCodeOutOfRange { + t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code) + } + }) + + t.Run("nil pointer", func(t *testing.T) { + var duration *time.Duration + err := MaxDuration(maxDuration)(duration) + if err != nil { + t.Errorf("expected no error for nil, got: %v", err) + } + }) +} + +func TestRangeDuration(t *testing.T) { + minDuration := 10 * time.Minute + maxDuration := 1 * time.Hour + + t.Run("duration within range", func(t *testing.T) { + duration := 30 * time.Minute + err := RangeDuration(minDuration, maxDuration)(&duration) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + }) + + t.Run("duration at minimum", func(t *testing.T) { + duration := 10 * time.Minute + err := RangeDuration(minDuration, maxDuration)(&duration) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + }) + + t.Run("duration at maximum", func(t *testing.T) { + duration := 1 * time.Hour + err := RangeDuration(minDuration, maxDuration)(&duration) + if err != nil { + t.Errorf("expected no error, got: %v", err) + } + }) + + t.Run("duration below minimum", func(t *testing.T) { + duration := 5 * time.Minute + err := RangeDuration(minDuration, maxDuration)(&duration) + if err == nil { + t.Fatal("expected validation error") + } + if err.Code != ErrorCodeOutOfRange { + t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code) + } + }) + + t.Run("duration above maximum", func(t *testing.T) { + duration := 2 * time.Hour + err := RangeDuration(minDuration, maxDuration)(&duration) + if err == nil { + t.Fatal("expected validation error") + } + if err.Code != ErrorCodeOutOfRange { + t.Errorf("expected error code %s, got %s", ErrorCodeOutOfRange, err.Code) + } + }) + + t.Run("nil pointer", func(t *testing.T) { + var duration *time.Duration + err := RangeDuration(minDuration, maxDuration)(duration) + if err != nil { + t.Errorf("expected no error for nil, got: %v", err) + } + }) +}