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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion acaad.abstractions/config/jest.coverage.config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"extends": "../node_modules/@acaad/toolchain/config/jest.coverage.config.json",
"setupFiles": []
"setupFiles": [],
"coveragePathIgnorePatterns": ["/tests/contracts/.*"]
}
8 changes: 7 additions & 1 deletion acaad.abstractions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"build": "heft build --clean",
"test": "heft test --clean",
"test:coverage": "heft test-coverage --clean",
"test:contracts": "heft build && node dist/cjs/tests/contracts/index.js",
"_phase:build": "heft run --only build -- --clean",
"_phase:test": "heft run --only test -- --clean"
},
Expand All @@ -43,6 +44,11 @@
"tslib": "~2.8.1",
"eslint": "^8.57.0",
"@types/heft-jest": "~1.0.6",
"@acaad/toolchain": "workspace:*"
"@acaad/toolchain": "workspace:*",
"commander": "~13.1.0",
"@commander-js/extra-typings": "~13.1.0",
"tsyringe": "~4.8.0",
"reflect-metadata": "~0.2.2",
"axios": "~1.7.9"
}
}
24 changes: 24 additions & 0 deletions acaad.abstractions/src/errors/OutcomeNotJsonError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { OutcomeNotParseableError } from './OutcomeNotParseableError';
import { AcaadCardinalityDefinition, AcaadResultTypeDefinition } from '../model';

export class OutcomeNotJsonError extends OutcomeNotParseableError {
_tag: string = 'OutcomeNotJsonError';

public errorDetails: unknown | undefined;

constructor(
expectedType: AcaadResultTypeDefinition,
expectedCardinality: AcaadCardinalityDefinition,
outcomeRaw: unknown,
errorDetails?: unknown
) {
super(
expectedType,
expectedCardinality,
outcomeRaw,
`Could not parse outcome ${outcomeRaw}. It does not seem to be valid json.`
);

this.errorDetails = errorDetails;
}
}
18 changes: 18 additions & 0 deletions acaad.abstractions/src/errors/OutcomeNotParseableError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { AcaadError } from './AcaadError';
import { AcaadCardinalityDefinition, AcaadResultTypeDefinition } from '../model';

export class OutcomeNotParseableError extends AcaadError {
_tag: string = 'OutcomeNotParseableError';

constructor(
expectedType: AcaadResultTypeDefinition,
expectedCardinality: AcaadCardinalityDefinition,
outcomeRaw: unknown,
message?: string
) {
super(
message ??
`Could not parse received outcome. Expected type ${expectedType} and cardinality ${expectedCardinality}, but received: '${outcomeRaw}'.`
);
}
}
2 changes: 2 additions & 0 deletions acaad.abstractions/src/errors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ export { AcaadFatalError } from './AcaadFatalError';
export { AcaadServerUnreachableError } from './AcaadServerUnreachableError';
export { CalloutError } from './CalloutError';
export { ConfigurationError } from './ConfigurationError';
export { OutcomeNotJsonError } from './OutcomeNotJsonError';
export { OutcomeNotParseableError } from './OutcomeNotParseableError';
export { ResponseSchemaError } from './ResponseSchemaError';
export { ResponseStatusCodeError } from './ResponseStatusCodeError';
15 changes: 13 additions & 2 deletions acaad.abstractions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export {
AcaadServerUnreachableError,
CalloutError,
ConfigurationError,
OutcomeNotJsonError,
OutcomeNotParseableError,
ResponseSchemaError,
ResponseStatusCodeError
} from './errors';
Expand All @@ -15,17 +17,21 @@ export {
OutboundStateChangeCallback,
ChangeType,
ITokenCache,
ConnectedServiceFunction
ConnectedServiceFunction,
IResponseParser
} from './interfaces';

export {
AcaadComponentMetadata,
AcaadDataMetadata,
AcaadOutcomeMetadata,
AcaadMetadata,
AcaadMetadataSchema,
AcaadOutcome,
AcaadOutcomeSchema,
AcaadUnitOfMeasure,
AcaadCardinalityDefinition,
AcaadResultTypeDefinition,
Component,
ComponentTypes,
ButtonComponent,
Expand All @@ -47,6 +53,8 @@ export {
ComponentCommandOutcomeEvent,
ComponentCommandOutcomeEventSchema,
EventFactory,
InboundStateUpdate,
OpenApiDefinitionFactory,
AnyAcaadEventSchema,
OpenApiDefinitionSchema,
AcaadServerMetadata,
Expand All @@ -57,11 +65,14 @@ export {
SchemaDefinition,
AcaadHostMapping,
InfoObjectSchema,
InfoObjectDefinition,
InfoObject,
OperationObjectSchema,
OperationObject,
PathItemObjectSchema,
PathItemObject
PathItemObject,
AcaadInfoMetadataSchema,
AcaadInfoMetadata
} from './model';

export { isNullOrUndefined } from './utils/Checks';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { Option } from 'effect/Option';
import { AcaadError } from '../errors';
import { Duration } from 'effect';
import { InboundStateUpdate } from '../model/InboundStateUpdate';

export type ChangeType = 'action' | 'query';

Expand Down Expand Up @@ -45,7 +46,11 @@ export interface IConnectedServiceAdapterFunctional {

registerStateChangeCallbackAsync(cb: OutboundStateChangeCallback, as: AbortSignal): Promise<void>;

updateComponentStateAsync(cd: ComponentDescriptor, obj: unknown, as: AbortSignal): Promise<void>;
updateComponentStateAsync(
cd: ComponentDescriptor,
inboundStateUpdate: InboundStateUpdate,
as: AbortSignal
): Promise<void>;

getConnectedServersAsync(as: AbortSignal): Promise<AcaadHost[]>;

Expand Down
13 changes: 13 additions & 0 deletions acaad.abstractions/src/interfaces/IResponseParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Effect } from 'effect';
import { AcaadOutcome, AcaadOutcomeMetadata, ComponentDescriptor } from '../model';
import { AcaadError } from '../errors';

export interface IResponseParser {
parseOutcomeEff(
componentDescriptor: ComponentDescriptor,
metadata: AcaadOutcomeMetadata,
outcome: AcaadOutcome
): Effect.Effect<unknown, AcaadError>;

parseSingleValue(metadata: AcaadOutcomeMetadata, value: unknown): Effect.Effect<unknown, AcaadError>;
}
2 changes: 2 additions & 0 deletions acaad.abstractions/src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export {
export { IConnectedServiceContext, ICsLogger } from './IConnectedServiceContext';

export { ITokenCache } from './ITokenCache';

export { IResponseParser } from './IResponseParser';
25 changes: 25 additions & 0 deletions acaad.abstractions/src/model/AcaadInfoMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Schema } from 'effect';

export const AcaadInfoMetadataSchema = Schema.Struct({
name: Schema.String,
os: Schema.String,
otlpEnabled: Schema.Boolean
});

export interface AcaadInfoMetadataDefinition extends Schema.Schema.Type<typeof AcaadInfoMetadataSchema> {}

export class AcaadInfoMetadata {
public name: string;
public operatingSystem: string;
public otlpEnabled: boolean;

public constructor(name: string, operatingSystem: string, otlpEnabled: boolean) {
this.name = name;
this.operatingSystem = operatingSystem;
this.otlpEnabled = otlpEnabled;
}

public static fromSchema(metadata: AcaadInfoMetadataDefinition) {
return new AcaadInfoMetadata(metadata.name, metadata.os, metadata.otlpEnabled);
}
}
73 changes: 68 additions & 5 deletions acaad.abstractions/src/model/AcaadMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AcaadComponentMetadata } from './AcaadComponentMetadata';
import { Schema } from 'effect';
import { Effect, Schema } from 'effect';
import { Option } from 'effect';
import { isNullOrUndefined } from '../utils/Checks';

Expand All @@ -8,41 +8,100 @@ const AcaadComponentMetadataSchema = Schema.Struct({
name: Schema.String
});

export const AcaadResultTypeSchema = Schema.Literal('String', 'Boolean', 'Long', 'Decimal');

export type AcaadResultTypeDefinition = typeof AcaadResultTypeSchema.Type;

export const AcaadCardinalitySchema = Schema.Union(Schema.Literal('Single'), Schema.Literal('Multiple'));

export type AcaadCardinalityDefinition = typeof AcaadCardinalitySchema.Type;

export const AcaadUnitOfMeasureSchema = Schema.String;

export const AcaadMetadataSchema = Schema.Struct({
component: AcaadComponentMetadataSchema,

actionable: Schema.UndefinedOr(Schema.Boolean),
queryable: Schema.UndefinedOr(Schema.Boolean),
idempotent: Schema.UndefinedOr(Schema.Boolean),
forValue: Schema.UndefinedOr(Schema.Unknown),
component: AcaadComponentMetadataSchema

/* TODO: Check if it is possible to cross-verify if onIff matches the value type below */
onIff: Schema.UndefinedOr(Schema.Unknown),

type: AcaadResultTypeSchema.pipe(
Schema.optional,
Schema.withDefaults({
constructor: () => 'String' as AcaadResultTypeDefinition,
decoding: () => 'String' as AcaadResultTypeDefinition
})
),
cardinality: AcaadCardinalitySchema.pipe(
Schema.optional,
Schema.withDefaults({
constructor: () => 'Single' as AcaadCardinalityDefinition,
decoding: () => 'Single' as AcaadCardinalityDefinition
})
),
unitOfMeasure: AcaadUnitOfMeasureSchema.pipe(Schema.optional)
});

export class AcaadMetadata {
export interface AcaadOutcomeMetadata {
type: AcaadResultTypeDefinition;
cardinality: AcaadCardinalityDefinition;

onIff: Option.Option<unknown>;
unitOfMeasure: Option.Option<string>;
}

export class AcaadMetadata implements AcaadOutcomeMetadata {
public path: string;
public method: string;

public component: AcaadComponentMetadata;

public type: AcaadResultTypeDefinition;
public cardinality: AcaadCardinalityDefinition;

public actionable?: boolean;
public queryable?: boolean;
public idempotent?: boolean;
public forValue: Option.Option<unknown>;

public onIff: Option.Option<unknown>;
public unitOfMeasure: Option.Option<string>;

public constructor(
path: string,
method: string,
component: AcaadComponentMetadata,
type: AcaadResultTypeDefinition,
cardinality: AcaadCardinalityDefinition,
idempotent?: boolean,
actionable?: boolean,
queryable?: boolean,
forValue?: unknown
forValue?: unknown,
onIff?: unknown,
unitOfMeasure?: string
) {
this.path = path;
this.method = method;

this.component = component;

this.type = type;
this.cardinality = cardinality;

this.idempotent = idempotent ?? false;
this.actionable = actionable ?? false;
this.queryable = queryable ?? false;

this.forValue = isNullOrUndefined(forValue) ? Option.none<unknown>() : Option.some(forValue);
this.onIff = isNullOrUndefined(onIff) ? Option.none<unknown>() : Option.some(onIff);

this.unitOfMeasure = isNullOrUndefined(unitOfMeasure)
? Option.none<string>()
: Option.some(unitOfMeasure!);
}

public static fromSchema(
Expand All @@ -54,10 +113,14 @@ export class AcaadMetadata {
path,
method,
schema.component,
schema.type,
schema.cardinality,
schema.idempotent,
schema.actionable,
schema.queryable,
schema.forValue
schema.forValue,
schema.onIff,
schema.unitOfMeasure
);
}

Expand Down
5 changes: 5 additions & 0 deletions acaad.abstractions/src/model/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export class Component extends Data.Class<{
return none();
}
}

getMetadata(): AcaadPopulatedMetadata {
// Oh well
return Chunk.toArray(this.metadata)[0];
}
}

export class ButtonComponent extends Component {
Expand Down
10 changes: 10 additions & 0 deletions acaad.abstractions/src/model/InboundStateUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { AcaadOutcome } from './AcaadOutcome';
import { AcaadMetadata } from './AcaadMetadata';

export interface InboundStateUpdate {
originalOutcome: AcaadOutcome;

determinedTargetState: unknown;

metadata: AcaadMetadata;
}
31 changes: 31 additions & 0 deletions acaad.abstractions/src/model/factories/OpenApiDefinitionFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Effect, pipe, Schema } from 'effect';
import { map, mapLeft } from 'effect/Either';
import { OpenApiDefinition, OpenApiDefinitionSchema } from '../open-api';
import { AcaadError, CalloutError, ResponseSchemaError } from '../../errors';

export class OpenApiDefinitionFactory {
public static verifyResponsePayload = (
data: unknown,
onExcessProperty: 'ignore' | 'error' | 'preserve' = 'ignore'
): Effect.Effect<OpenApiDefinition, AcaadError> => {
if (data) {
const result = Schema.decodeUnknownEither(OpenApiDefinitionSchema)(data, {
onExcessProperty
});

return pipe(
result,
mapLeft(
(error) =>
new ResponseSchemaError(
'The server did not respond according to the acaad openapi extension. This is caused either by an incompatible version or another openapi json that was discovered.',
error
)
),
map((val) => OpenApiDefinition.fromSchema(val))
);
}

return Effect.fail(new CalloutError('No or invalid data received from the server.'));
};
}
1 change: 1 addition & 0 deletions acaad.abstractions/src/model/factories/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { EventFactory, AnyAcaadEventSchema } from './EventFactory';
export { OpenApiDefinitionFactory } from './OpenApiDefinitionFactory';
Loading