Referencing yourself inside of a struct - struct

Let's say I have a type that's a struct like so:
type Authorization struct {
Username string
Password string
Handler func(http.HandlerFunc) http.HandlerFunc
}
And I have an array of these:
type Authorizations map[string]*Authorization
I want to be able to do something like this:
var auth = Authorizations{
"test": *Authorization{
"someusername",
"somepassword",
self.BasicAuth,
},
}
Assume that self.BasicAuth (which obviously doesn't work) is a method on the Authorization type. What is the syntactically correct way to do this?

You can't refer to a value inside its own declaration. You need to initialize the value first, then you can assign the method you want to use to Handler.
testAuth := &Authorization{
Username: "someusername",
Password: "somepassword",
}
testAuth.Handler = testAuth.HandleFunc
auths := Authorizations{
"test": testAuth,
}

Related

TypeScript shared configuration object with typehinting

I am developing an API library and I am curious about how should endpoint configuration problem should be approached in Node.js with TypeScript. I want all endpoint configuration to be contained within one entity.
I currently have this approach in place:
ApiConstants.ts
------------------------
const BASE_DOMAIN = 'https://api.example.com';
export default Object.freeze({
BASE_DOMAIN: {
V1: `${BASE_DOMAIN}/v0.1`,
V2: `${BASE_DOMAIN}/v0.2`,
V3: `${BASE_DOMAIN}/v0.3`,
},
PATH: {
CATS: '/animals/cats',
},
});
It does the job, I can use it in any class by importing it and accessing the values. The problem is that I want to restrict functions to only accept values declared within this object. When request constructing function should display invalid type intellisense when value is passed which is not a part of this object.
Desired type would look something like this. Path must be declared within ApiConstants.PATH object.
function makeRequest(path: ApiConstants.PATH) {
...
}
How can such behavior be achieved?
function makeRequest(path: keyof typeof ApiConstants.PATH) {
// ...
}
See in Playground
In general, you probably want to do something like
const ApiConstants = Object.freeze({
BASE_DOMAIN: {
V1: `${BASE_DOMAIN}/v0.1`,
V2: `${BASE_DOMAIN}/v0.2`,
V3: `${BASE_DOMAIN}/v0.3`,
},
PATH: {
CATS: '/animals/cats',
},
});
type ApiConstants = typeof ApiConstants;
export default ApiConstants;
to avoid having to use typeof ApiConstants all the time. Note that this means ApiConstants is both a value and a type—Typescript is OK with this, and will know from context what you’re doing, but some programmers find it confusing. A common naming convention is to use an initial lowercase letter for the value, and an initial uppercase for the type, as in const apiConstants = /*…*/; and type ApiConstants = typeof apiConstants;.
On the other hand, it’s kind of convenient that your default export is both the value and the type.
You might also want to add
export type ApiPaths = keyof ApiConstants['PATH'];
We use ['PATH'] because we’re using the type ApiConstants here, not the value. We could still use the value but we’d have to add typeof to it again, as in keyof typeof ApiConstants.PATH.

Remove propertis not present in interface with nodejs typescript

I'm using node.js with typescript and I have a question
It is possible to remove properties that are not present in an interface when casting an object.
interface IFooReal {
prop1: string;
}
const objTMP = {prop1: 'foo001', prop2: 'foo002', prop3: 'foo003'};
cont barr = objTMP as IFooReal; //or do someting to remove properties not presente in interface
console.log(barr);
When execute console.log(barr); the result is {prop1: 'foo001'}
Using a type assertion like objTMP as IFooReal doesn't change anything about objTMP, it only tells TypeScript "please treat this as an IFooReal".
TypeScript's purpose is just annotating your code in a way that lets it check it for type safety. It doesn't modify your code, or add any execution of its own (with limited exceptions, like enums). When TypeScript is compiled into JavaScript, the process is mostly just removing those annotations.
If you want to remove all but a subset of properties from an object, then you will need to iterate through its properties and remove any that don't match the properties you want to keep. Something like this, for example:
interface IFooReal {
prop1: string;
}
const objTMP = {
prop1: 'foo001',
prop2: 'foo002',
prop3: 'foo003'
};
// You may want to get this by calling Object.keys() on another object?
const propertiesToKeep = ['prop1'];
for (let prop in objTMP) {
if (propertiesToKeep.includes(prop) === false) {
delete (objTMP as {[key:string]: any})[prop];
}
}
console.log(objTMP);
TypeScript playground
I've had to use the type assertion objTMP as {[key: string]: any}) here because otherwise TypeScript would complain that you're attempting to access a property on objTMP using a variable with type string, when TypeScript has implicitly typed it as { prop1: string, prop2: string, prop3: string } so only values of type 'prop1'|'prop2'|'prop3' can be used to access its properties.
That type assertion basically just tells TypeScript "let me try to access properties on this object using any string as the key, and don't worry about what type that property has".

Get keys from interface in a generic function

I need to get types from an interface by doing something like below, but I need to do it inside a function. Is there any way to do this using typescript generics?
I need the function to pass request bodies, along with an interface specifying their types and verify that the request body has the necessary items as well as correct format.
Note: I am using tsoa with express, so any other library or technique to properly validate request bodies would be fine.
interface someInterface {
foo: string;
bar: number;
}
const testObject: someInterface = req.body;
verifyObject(testObject);
/*
ensure foo and bar are of correct type, length etc.
(I will verify types, I just need a way of
getting the interface keys in a reusable function.)
*/
function verifyObject<T>(obj: T): void {
class temp implements T {} // does not work
const keys = Object.keys(new temp());
// use keys
}
You almost have it - made a generic function, so its param will be the object of the Interface, and accessing keys of the object is, well, you know it:
function verifyObject<T>(obj: T): void {
const keys = Object.keys(obj);
}
verifyObject<someInterface>(someObj);

TS: Cannot invoke an expression whose type lacks a call signature when defined dynamically, but it works

I'm still quite new to typescript, so please be gentle with me if I'm doing something with no sense for this technology!
The problem that I'm trying to solve is having a dynamic way to define how my application errors should be structured, but leaving to the users the faculty to enrich the messages.
So I tried to create this logic in a module that could be extended easily from the application, but I'm currently facing the problem:
Error:(35, 18) TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'ErrorMessage' has no compatible call signatures.
What I thought it was a good idea (but please tell me if I'm wrong), was to use a register and a map to have the possibility to extend this mapping every time I want. So I created my ErrorMessage interface to be like the following:
export interface ErrorMessage {
actionMessage: string;
actionSubject: string;
originalErrorMessage?: string;
toString: () => string;
}
and a register for these, called ErrorResponseRegister, as it follows:
export enum defaultErrors {
ExceptionA = 'ExceptionA',
ExceptionB = 'ExceptionB',
}
export class ErrorResponseRegister {
private mapping: Map<string, ErrorMessage>;
constructor() {
this.mapping = new Map()
.set(defaultErrors.ExceptionA, exceptionAErrorMessage)
.set(defaultErrors.ExceptionB, exceptionBErrorMessage);
}
}
So at the end, every ErrorMessage function should look like:
export function exceptionAErrorMessage(originalErrorMessage?: string): ErrorMessage {
return {
enrichment1: "Something happened",
enrichment2: "in the application core",
originalErrorMessage: originalErrorMessage,
toString(): string {
return `${this.enrichment1} ${this.enrichment2}. Original error message: ${originalErrorMessage}`;
},
};
}
Please note I haven't used classes for this ones, as it doesn't really need to be instantiated
and I can have a bunch of them where the toString() method can vary. I just want to enforce the errors should have an enrichment1 and enrichment2 that highlight the problem in a better way for not-technical people.
So, now, back to code. When I'm trying to use the exceptionAErrorMessage statically, I can't see any problem:
console.log(exceptionAErrorMessage(originalErrorMessage).toString())
But when I try dynamically, using the map defined in the ErrorResponseRegister, something weird happens:
// In ErrorResponseRegister
public buildFor(errorType: string, originalErrorMessage?: string): Error {
const errorMessageBuilder = this.mapping.get(errorType);
if (errorMessageBuilder) {
return errorMessageBuilder(originalErrorMessage).toString();
}
return "undefined - do something else";
}
The code works as expected, the error returned is in the right format, so the toString function is executed correctly.
BUT, the following error appears in the IDE:
Error:(32, 18) TS2349: Cannot invoke an expression whose type lacks a call signature. Type 'ErrorMessage' has no compatible call signatures.
The line that causes the problem is
errorMessageBuilder(originalPosErrorMessage).toString()
Can someone help me to understand what I'm doing wrong?
It looks like your problem is you've mistyped mapping... it doesn't hold ErrorMessage values; it holds (x?: string)=>ErrorMessage values:
private mapping: Map<string, (x?: string) => ErrorMessage>;
What's unfortunate is that you initialize this variable via new Map().set(...) instead of the using an iterable constructor argument.
The former returns a Map<any, any> which is trivially assignable to mapping despite the mistyping. That is, you ran smack into this known issue where the standard library's typings for the no-argument Map constructor signature produces Map<any, any> which suppresses all kinds of otherwise useful error messages. Perhaps that will be fixed one day, but for now I'd suggest instead that you use the iterable constructor argument, whose type signature declaration will infer reasonable types for the keys/values:
constructor() {
this.mapping = new Map([
[defaultErrors.ExceptionA, exceptionAErrorMessage],
[defaultErrors.ExceptionB, exceptionBErrorMessage]
]); // inferred as Map<defaultErrors, (orig?: string)=>ErrorMessage>
}
If you had done so, it would have flagged the assignment as an error with your original typing for mapping (e.g., Type 'Map<defaultErrors, (originalErrorMessage?: string | undefined) => ErrorMessage>' is not assignable to type 'Map<string, ErrorMessage>'.) Oh well!
Once you make those changes, things should behave more reasonably for you. Hope that helps; good luck!
Link to code

Generics and Anonymous types

I have a function,
public static IPagedResponse<T> GetPagedResponse<T, TAnon>(
this IQueryable<TAnon> query,
QueryableRequestMessage request)
where T : class
{
//...
}
I'm trying to pass query as an IQueryable of an anonymous type.
var query = _repository.All.Select(
i => new //anon type
{
i.Id,
i.Name,
}
);
var result = query.GetPagedResponse<EftInterfaceDto, ??????>(request);
The issue is I don't know what to put in place of ??????? It can't seem to infer it. And any combinations using .GetType() or typeof() I have tried, failed.
I tried changing the function to be IQueryable<dynamic> but that resulted in other errors, about dynamic not being allowed in Expression trees.
HACK:
I can make it work if I change my function to this:
public static IPagedResponse<T> GetPagedResponse<T, TAnon>(
this IQueryable<TAnon> query,
QueryableRequestMessage request,
T typeSample)
where T : class
{
//...
}
And then pass in an instance of T
var result = query.GetPagedResponse(request, new SomeClassOfT());
This way, I can use type inference to determine the anonymous type TAnon, and don't need to be explicit in the call to the generic (no <types> required).
However, I don't want to do this, as it's clearly not clear what I'm doing.

Resources