import { Observable } from 'rxjs' ; import { map } from 'rxjs/operators' ; import { ODataApi } from '../api' ; import { PARAM_SEPARATOR , QUERY_SEPARATOR , VALUE_SEPARATOR , } from '../constants' ; import { ODataCollection , ODataModel } from '../models' ; import { ODataCallable , ODataStructuredType , ODataStructuredTypeParser , } from '../schema' ; import { ODataSchemaElement } from '../schema/element' ; import { OptionsHelper , Parser , QueryOptionNames } from '../types' ; import { Http , Objects , Types } from '../utils/index' ; import { ODataPathSegments , ODataPathSegmentsHandler } from './path' ; import { isQueryCustomType , ODataQueryOptions , ODataQueryOptionsHandler , QueryCustomType , } from './query' ; import { ODataRequest } from './request' ; import { ODataEntitiesAnnotations , ODataEntityAnnotations , ODataResponse , } from './responses/index' ; import { ODataOptions } from './types' ; export type EntityKey < T > = { readonly [ P in keyof T ] ? : T [ P ] ; } | QueryCustomType | string | number ; export class ODataResource < T > { public api : ODataApi ; public schema ? : ODataSchemaElement ; protected pathSegments : ODataPathSegments ; protected queryOptions : ODataQueryOptions < T > ; constructor ( api , { segments , query , schema , } = { } ) { this . api = api ; this . pathSegments = segments || new ODataPathSegments ( ) ; this . queryOptions = query || new ODataQueryOptions ( ) ; this . schema = schema ; } returns type ( ) { return this . pathSegments . last ( ) ?. type ( ) ; } returns returnType ( ) { return this . pathSegments . last ( ) ?. type ( ) ; } returns types ( ) { return this . pathSegments . types ( ) ; } returns hasKey ( ) { return Boolean ( this . pathSegments . last ( { key : true } ) ?. hasKey ( ) ) ; } clearKey ( ) { return this . pathSegments . last ( { key : true } ) ?. clearKey ( ) ; } asModel < M extends ODataModel < T > > ( entity ? , { annots , reset } = { } ) { let resource = this as ODataResource < T > ; const type = annots ?. type || this . returnType ( ) ; if ( type === undefined ) throw Error ( '' ) ; const Model = this . api . modelForType ( type ) ; let entitySet = annots ?. entitySet ; if ( entitySet !== undefined ) { resource = this . api . entitySet < T > ( entitySet ) . entity ( entity as Partial < T > ) ; resource . query ( ( q ) => q . apply ( this . queryOptions . toQueryArguments ( ) ) ) ; } return new Model ( entity , { resource , annots , reset } ) as M ; } asCollection < M extends ODataModel < T > , C extends ODataCollection < T , M > > ( entities ? , { annots , reset , } = { } ) { let resource = this as ODataResource < T > ; const type = annots ?. type || this . returnType ( ) ; if ( type === undefined ) throw Error ( '' ) ; const Collection = this . api . collectionForType ( type ) ; let path = annots ?. entitySet ; if ( path !== undefined ) { resource = this . api . entitySet < T > ( path ) ; resource . query ( ( q ) => q . apply ( this . queryOptions . toQueryArguments ( ) ) ) ; } return new Collection ( entities , { resource , annots , reset } ) as C ; } isSubtypeOf ( other ) { return ( this . schema !== undefined && other . schema !== undefined && this . schema ?. isSubtypeOf ( other . schema ) ) ; } isEqualTo ( other , test ? ) { const [ selfPath , selfParams ] = this . pathAndParams ( ) ; const [ otherPath , otherParams ] = other . pathAndParams ( ) ; return test === 'path' ? otherPath === selfPath : test === 'params' ? Types . isEqual ( selfParams , otherParams ) : otherPath === selfPath && Types . isEqual ( selfParams , otherParams ) ; } pathAndParams ( escape = false ) { const [ spath , sparams ] = this . pathSegments . pathAndParams ( escape ) ; const [ , qparams ] = this . queryOptions . pathAndParams ( escape ) ; return [ spath , { ... sparams , ... qparams } ] ; } endpointUrl ( params = true ) { if ( params ) { return `${ this . api . serviceRootUrl }${ this }` ; } else { let [ path ] = this . pathAndParams ( ) ; return `${ this . api . serviceRootUrl }${ path }` ; } } toString ( ) { let [ path , params ] = this . pathAndParams ( ) ; let queryString = Object . entries ( params ) . map ( ( e ) => `${ e [ 0 ] }${ VALUE_SEPARATOR }${ e [ 1 ] }` ) . join ( PARAM_SEPARATOR ) ; return queryString ? `${ path }${ QUERY_SEPARATOR }${ queryString }` : path ; } clone < R extends ODataResource < T > > ( ) { const Ctor = this . constructor as typeof ODataResource ; return new Ctor ( this . api , { schema : this . schema , segments : this . cloneSegments ( ) , query : this . cloneQuery < T > ( ) , } ) as R ; } deserialize ( value , options ? ) { const resourceType = this . returnType ( ) ; const resourceSchema = this . schema ; const _p = ( value , options ) : Parser < T > | undefined => { const dataType = Types . isPlainObject ( value ) ? options . helper . type ( value ) : undefined ; if ( dataType !== undefined ) { return this . api . parserForType < T > ( dataType ) ; } else if ( resourceSchema !== undefined && 'parser' in resourceSchema ) { return ( < any > resourceSchema ) . parser as Parser < T > | undefined ; } else if ( resourceType !== undefined ) { return this . api . parserForType < T > ( resourceType ) ; } return undefined ; } ; const _d = ( value , options ) => { const parser = _p ( value , options ) ; return parser !== undefined && 'deserialize' in parser ? parser . deserialize ( value , options ) : value ; } ; return Array . isArray ( value ) ? value . map ( ( v ) => _d ( v , options || this . api . options ) ) : _d ( value , options || this . api . options ) ; } serialize ( value , options ? ) { const resourceType = this . type ( ) ; const resourceSchema = this . schema ; const _p = ( value , options ) : Parser < T > | undefined => { const dataType = Types . isPlainObject ( value ) ? options . helper . type ( value ) : undefined ; if ( dataType !== undefined ) { return this . api . parserForType < T > ( dataType ) ; } else if ( resourceSchema !== undefined && 'parser' in resourceSchema ) { return ( < any > resourceSchema ) . parser as Parser < T > | undefined ; } else if ( resourceType !== undefined ) { return this . api . parserForType < T > ( resourceType ) ; } return undefined ; } ; const _s = ( value , options ) => { const parser = _p ( value , options ) ; return parser !== undefined && 'serialize' in parser ? parser . serialize ( value , options ) : value ; } ; return Array . isArray ( value ) ? value . map ( ( v ) => _s ( v , options || this . api . options ) ) : _s ( value , options || this . api . options ) ; } encode ( value , options ? ) { const resourceType = this . type ( ) ; const resourceSchema = this . schema ; const _p = ( value , options ) : Parser < T > | undefined => { const dataType = Types . isPlainObject ( value ) ? options . helper . type ( value ) : undefined ; if ( dataType !== undefined ) { return this . api . parserForType < T > ( dataType ) ; } else if ( resourceSchema !== undefined && 'parser' in resourceSchema ) { return ( < any > resourceSchema ) . parser as Parser < T > | undefined ; } else if ( resourceType !== undefined ) { return this . api . parserForType < T > ( resourceType ) ; } return undefined ; } ; const _e = ( value , options ) => { const parser = _p ( value , options ) ; return parser !== undefined && 'encode' in parser ? parser . encode ( value , options ) : value ; } ; return Array . isArray ( value ) ? value . map ( ( v ) => _e ( v , options || this . api . options ) ) : _e ( value , options || this . api . options ) ; } toJSON ( ) { return { segments : this . pathSegments . toJSON ( ) , options : this . queryOptions . toJSON ( ) , } ; } cloneSegments ( ) { return this . pathSegments . clone ( ) ; } clearQuery ( ) { this . queryOptions . clear ( ) ; } cloneQuery < P > ( ) { return this . queryOptions . clone < P > ( ) ; } param f returns segment ( f ) { f ( new ODataPathSegmentsHandler < T > ( this . pathSegments ) ) ; return this ; } param f query ( f ) { f ( new ODataQueryOptionsHandler < T > ( this . queryOptions ) ) ; return this ; } static resolveKey < T > ( value , schema ? ) { if ( isQueryCustomType ( value ) ) { return value ; } else if ( Types . isPlainObject ( value ) ) { return schema instanceof ODataStructuredType ? schema . resolveKey ( value ) : Objects . resolveKey ( value ) ; } return value as EntityKey < T > | undefined ; } protected resolveKey ( value ) { return ODataResource . resolveKey < T > ( value , this . schema as ODataStructuredType < T > ) ; } protected request ( method , options ) { const apiOptions = this . api . options ; let params = options . params || { } ; if ( options . withCount ) { params = Http . mergeHttpParams ( params , apiOptions . helper . countParam ( ) ) ; } let etag = options . etag ; if ( etag === undefined && Types . isPlainObject ( options . body ) ) { etag = apiOptions . helper . etag ( options . body ) ; } const request = new ODataRequest ( { method , etag , body : options . body , api : this . api , resource : this , observe : 'response' , headers : options . headers , reportProgress : options . reportProgress , params : params , responseType : options . responseType , fetchPolicy : options . fetchPolicy , withCredentials : options . withCredentials , bodyQueryOptions : options . bodyQueryOptions , } ) ; const res$ = this . api . request ( request ) ; switch ( options . responseType ) { case 'entities' : return res$ . pipe ( map ( ( res ) => res . entities ( ) ) ) ; case 'entity' : return res$ . pipe ( map ( ( res ) => res . entity ( ) ) ) ; case 'property' : return res$ . pipe ( map ( ( res ) => res . property ( ) ) ) ; case 'value' : return res$ . pipe ( map ( ( res ) => res . value ( ) as T ) ) ; default : return res$ . pipe ( map ( ( res ) => res . body ) ) ; } } protected get ( options = { } ) { return this . request ( 'GET' , options ) ; } protected post ( body , options = { } ) { return this . request ( 'POST' , { body , ... options } ) ; } protected put ( body , options = { } ) { return this . request ( 'PUT' , { body , ... options } ) ; } protected patch ( body , options = { } ) { return this . request ( 'PATCH' , { body , ... options } ) ; } protected delete ( options = { } ) { return this . request ( 'DELETE' , options ) ; } }