11import { Literal , NamedNode , Quad_Subject , Store } from 'n3' ;
22import { DC , RDF , REPORT } from '../Vocabularies' ;
3- import { ActivationState , PolicyReport , PremiseReport , PremiseReportType , RuleReport , RuleReportType , SatisfactionState } from './ComplianceReportTypes' ;
3+ import { ActivationState , AttemptState , DeonticState , PerformanceState , PolicyReport , PremiseReport , PremiseReportType , RuleReport , RuleReportType , SatisfactionState } from './ComplianceReportTypes' ;
44import { Quad } from '@rdfjs/types' ;
55
66/**
@@ -47,14 +47,48 @@ export function parseComplianceReport(identifier: Quad_Subject, store: Store): P
4747*/
4848export function parseRuleReport ( identifier : Quad_Subject , store : Store ) : RuleReport {
4949 const premiseNodes = store . getObjects ( identifier , REPORT . premiseReport , null ) as NamedNode [ ] ;
50+ const activationState = parseSingleState (
51+ store ,
52+ identifier ,
53+ REPORT . activationState ,
54+ ActivationState ,
55+ ) ;
56+
57+ const attemptState = parseSingleState (
58+ store ,
59+ identifier ,
60+ REPORT . attemptState ,
61+ AttemptState ,
62+ ) ;
63+
64+ const performanceState = parseSingleState (
65+ store ,
66+ identifier ,
67+ REPORT . performanceState ,
68+ PerformanceState ,
69+ ) ;
70+
71+ const deonticState = parseSingleState (
72+ store ,
73+ identifier ,
74+ REPORT . deonticState ,
75+ DeonticState ,
76+ ) ;
77+
78+
5079 return {
5180 id : identifier as NamedNode ,
5281 type : store . getObjects ( identifier , RDF . type , null ) [ 0 ] . value as RuleReportType ,
53- activationState : store . getObjects ( identifier , REPORT . activationState , null ) [ 0 ] . value as ActivationState ,
5482 requestedRule : store . getObjects ( identifier , REPORT . ruleRequest , null ) [ 0 ] as NamedNode ,
5583 rule : store . getObjects ( identifier , REPORT . rule , null ) [ 0 ] as NamedNode ,
56- premiseReport : premiseNodes . map ( ( prem ) => parsePremiseReport ( prem , store ) )
57- }
84+ premiseReport : premiseNodes . map ( ( prem ) => parsePremiseReport ( prem , store ) ) ,
85+ conditionReport : store . getObjects ( identifier , REPORT . conditionReport , null ) as NamedNode [ ] ,
86+
87+ activationState,
88+ attemptState,
89+ performanceState,
90+ deonticState
91+ } ;
5892}
5993
6094/**
@@ -69,21 +103,87 @@ export function parseRuleReport(identifier: Quad_Subject, store: Store): RuleRep
69103 * @throws Error if a circular premise report is detected.
70104 */
71105export function parsePremiseReport (
72- identifier : Quad_Subject ,
73- store : Store ,
74- visited : Set < string > = new Set ( )
106+ identifier : Quad_Subject ,
107+ store : Store ,
108+ visited : Set < string > = new Set ( )
75109) : PremiseReport {
76110 const idStr = ( identifier as NamedNode ) . value ;
77111 if ( visited . has ( idStr ) ) {
78112 throw new Error ( `Circular premise report detected at ${ idStr } ` ) ;
79113 }
80114 visited . add ( idStr ) ;
81115
82- const nestedPremises = store . getObjects ( identifier , REPORT . PremiseReport , null ) as NamedNode [ ] ;
83- return {
116+ const nestedPremises = store . getObjects ( identifier , REPORT . premiseReport , null ) as NamedNode [ ] ;
117+
118+ let premiseReport : PremiseReport = {
84119 id : identifier as NamedNode ,
85120 type : store . getObjects ( identifier , RDF . type , null ) [ 0 ] . value as PremiseReportType ,
86- premiseReport : nestedPremises . map ( ( prem ) => parsePremiseReport ( prem , store ) ) ,
87- satisfactionState : store . getObjects ( identifier , REPORT . satisfactionState , null ) [ 0 ] . value as SatisfactionState
121+ premiseReport : nestedPremises . map ( ( prem ) => parsePremiseReport ( prem , store , visited ) ) ,
122+ satisfactionState : parseSingleState ( store , identifier , REPORT . satisfactionState , SatisfactionState )
123+ }
124+
125+ if ( premiseReport . type === PremiseReportType . ConstraintReport ) {
126+ if ( store . getObjects ( identifier , REPORT . constraint , null ) . length !== 1 ) throw new Error ( "Expected one constraint" ) ;
127+ const constraintID = store . getObjects ( identifier , REPORT . constraint , null ) [ 0 ] as NamedNode ;
128+ premiseReport . constraint = constraintID
88129 }
130+ return premiseReport
131+ }
132+
133+ /**
134+ * Extracts exactly one state value from an N3 store and validates it
135+ * against a provided enum of allowed values.
136+ *
137+ * This helper enforces three guarantees:
138+ * 1. At most one value exists for the given subject–predicate pair.
139+ * 2. If a value exists, it must match one of the allowed enum values.
140+ * 3. Returns `undefined` when no value is present.
141+ *
142+ * The human‑readable label used in error messages is automatically derived
143+ * from the predicate URI by taking the last path or fragment segment.
144+ *
145+ * @template T - An enum-like object whose values represent valid states.
146+ *
147+ * @param store - The N3 store containing RDF quads.
148+ * @param subject - The subject node from which the state is extracted.
149+ * @param predicate - The predicate identifying the state property (as a URI string).
150+ * @param allowed - The enum of allowed values for this state.
151+ *
152+ * @returns The validated state value, or `undefined` if no value is present.
153+ *
154+ * @throws Error
155+ * Thrown when:
156+ * - More than one value is found for the given predicate.
157+ * - The found value does not match any allowed enum value.
158+ */
159+ function parseSingleState < T extends Record < string , string > > (
160+ store : Store ,
161+ subject : NamedNode | Quad_Subject ,
162+ predicate : string ,
163+ allowed : T
164+ ) : T [ keyof T ] | undefined {
165+ // Derive label from last path or fragment segment
166+ const label =
167+ predicate . split ( '#' ) . pop ( ) ?. split ( '/' ) . pop ( ) ??
168+ predicate ;
169+
170+ const objects = store . getObjects ( subject , predicate , null ) ;
171+
172+ if ( objects . length > 1 ) {
173+ throw new Error ( `Multiple ${ label } values found for ${ subject . value } ` ) ;
174+ }
175+
176+ if ( objects . length === 0 ) {
177+ return undefined ;
178+ }
179+
180+ const value = objects [ 0 ] . value ;
181+ const allowedValues = Object . values ( allowed ) ;
182+
183+ if ( ! allowedValues . includes ( value ) ) {
184+ throw new Error ( `Invalid ${ label } value for ${ subject . value } : ${ value } ` ) ;
185+ }
186+
187+ return value as T [ keyof T ] ;
89188}
189+
0 commit comments