From 46b4293e35f1e1f3a2e591131423e8a554b8208a Mon Sep 17 00:00:00 2001 From: Alessandro Ros Date: Sun, 25 Jan 2026 19:35:43 +0100 Subject: [PATCH] api: improve openapi definition (#5380) add uint64, uint, uuid, move WebRTCICEServer into dedicated schema --- api/openapi.yaml | 183 +++++++++++---------- internal/api/api.go | 4 +- internal/api/api_config_paths.go | 4 +- internal/api/api_hls_test.go | 4 +- internal/api/api_paths_test.go | 14 +- internal/api/api_recordings.go | 4 +- internal/api/api_rtmp_test.go | 4 +- internal/api/api_rtsp_test.go | 8 +- internal/api/api_srt_test.go | 4 +- internal/api/api_webrtc_test.go | 4 +- internal/core/path.go | 12 +- internal/core/path_manager.go | 4 +- internal/core/source_redirect.go | 4 +- internal/defs/api.go | 86 +++++----- internal/defs/reader.go | 2 +- internal/defs/source.go | 2 +- internal/hooks/on_connect.go | 2 +- internal/hooks/on_read.go | 2 +- internal/hooks/on_ready.go | 2 +- internal/metrics/metrics_test.go | 16 +- internal/servers/hls/muxer.go | 4 +- internal/servers/hls/server.go | 4 +- internal/servers/rtmp/conn.go | 20 ++- internal/servers/rtmp/server.go | 4 +- internal/servers/rtsp/conn.go | 20 +-- internal/servers/rtsp/server.go | 8 +- internal/servers/rtsp/session.go | 18 +- internal/servers/srt/conn.go | 15 +- internal/servers/srt/server.go | 4 +- internal/servers/webrtc/server.go | 4 +- internal/servers/webrtc/session.go | 13 +- internal/staticsources/handler.go | 4 +- internal/staticsources/hls/source.go | 4 +- internal/staticsources/mpegts/source.go | 4 +- internal/staticsources/rpicamera/source.go | 8 +- internal/staticsources/rtmp/source.go | 4 +- internal/staticsources/rtp/source.go | 4 +- internal/staticsources/rtsp/source.go | 4 +- internal/staticsources/srt/source.go | 4 +- internal/staticsources/webrtc/source.go | 4 +- internal/testapidocs/apidocs_test.go | 147 ++++++++++++----- 41 files changed, 379 insertions(+), 287 deletions(-) diff --git a/api/openapi.yaml b/api/openapi.yaml index 18c46f1b..db48d9ce 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -91,7 +91,7 @@ components: format: int64 udpReadBufferSize: type: integer - format: int64 + format: uint64 runOnConnect: type: string runOnConnectRestart: @@ -336,16 +336,7 @@ components: webrtcICEServers2: type: array items: - type: object - properties: - url: - type: string - username: - type: string - password: - type: string - clientOnly: - type: boolean + $ref: '#/components/schemas/WebRTCICEServer' webrtcHandshakeTimeout: type: string webrtcTrackGatherTimeout: @@ -429,15 +420,15 @@ components: # Raspberry Pi Camera source rpiCameraCamID: type: integer - format: int64 + format: uint64 rpiCameraSecondary: type: boolean rpiCameraWidth: type: integer - format: int64 + format: uint64 rpiCameraHeight: type: integer - format: int64 + format: uint64 rpiCameraHFlip: type: boolean rpiCameraVFlip: @@ -469,7 +460,7 @@ components: type: string rpiCameraShutter: type: integer - format: int64 + format: uint64 rpiCameraMetering: type: string rpiCameraGain: @@ -502,7 +493,7 @@ components: type: string rpiCameraFlickerPeriod: type: integer - format: int64 + format: uint64 rpiCameraTextOverlayEnable: type: boolean rpiCameraTextOverlay: @@ -511,10 +502,10 @@ components: type: string rpiCameraIDRPeriod: type: integer - format: int64 + format: uint64 rpiCameraBitrate: type: integer - format: int64 + format: uint64 rpiCameraHardwareH264Profile: type: string rpiCameraHardwareH264Level: @@ -525,7 +516,7 @@ components: type: string rpiCameraMJPEGQuality: type: integer - format: int64 + format: uint64 # Hooks runOnInit: @@ -594,10 +585,10 @@ components: type: string bytesReceived: type: integer - format: int64 + format: uint64 bytesSent: type: integer - format: int64 + format: uint64 readers: type: array items: @@ -666,7 +657,7 @@ components: type: string bytesSent: type: integer - format: int64 + format: uint64 HLSMuxerList: type: object @@ -717,6 +708,7 @@ components: properties: id: type: string + format: uuid created: type: string remoteAddr: @@ -730,10 +722,10 @@ components: type: string bytesReceived: type: integer - format: int64 + format: uint64 bytesSent: type: integer - format: int64 + format: uint64 RTMPConnList: type: object @@ -754,18 +746,20 @@ components: properties: id: type: string + format: uuid created: type: string remoteAddr: type: string bytesReceived: type: integer - format: int64 + format: uint64 bytesSent: type: integer - format: int64 + format: uint64 session: type: string + format: uuid nullable: true tunnel: type: string @@ -789,6 +783,7 @@ components: properties: id: type: string + format: uuid created: type: string remoteAddr: @@ -808,34 +803,34 @@ components: nullable: true bytesReceived: type: integer - format: int64 + format: uint64 bytesSent: type: integer - format: int64 + format: uint64 rtpPacketsReceived: type: integer - format: int64 + format: uint64 rtpPacketsSent: type: integer - format: int64 + format: uint64 rtpPacketsLost: type: integer - format: int64 + format: uint64 rtpPacketsInError: type: integer - format: int64 + format: uint64 rtpPacketsJitter: type: number format: double rtcpPacketsReceived: type: integer - format: int64 + format: uint64 rtcpPacketsSent: type: integer - format: int64 + format: uint64 rtcpPacketsInError: type: integer - format: int64 + format: uint64 RTSPSessionList: type: object @@ -856,6 +851,7 @@ components: properties: id: type: string + format: uuid created: type: string remoteAddr: @@ -869,121 +865,121 @@ components: type: string packetsSent: type: integer - format: int64 + format: uint64 description: The total number of sent DATA packets, including retransmitted packets packetsReceived: type: integer - format: int64 + format: uint64 description: The total number of received DATA packets, including retransmitted packets packetsReceivedBelated: type: integer - format: int64 + format: uint64 packetsSentUnique: type: integer - format: int64 + format: uint64 description: The total number of unique DATA packets sent by the SRT sender packetsReceivedUnique: type: integer - format: int64 + format: uint64 description: The total number of unique original, retransmitted or recovered by the packet filter DATA packets received in time, decrypted without errors and, as a result, scheduled for delivery to the upstream application by the SRT receiver. packetsSendLoss: type: integer - format: int64 + format: uint64 description: The total number of data packets considered or reported as lost at the sender side. Does not correspond to the packets detected as lost at the receiver side. packetsReceivedLoss: type: integer - format: int64 + format: uint64 description: The total number of SRT DATA packets detected as presently missing (either reordered or lost) at the receiver side packetsRetrans: type: integer - format: int64 + format: uint64 description: The total number of retransmitted packets sent by the SRT sender packetsReceivedRetrans: type: integer - format: int64 + format: uint64 description: The total number of retransmitted packets registered at the receiver side packetsSentACK: type: integer - format: int64 + format: uint64 description: The total number of sent ACK (Acknowledgement) control packets packetsReceivedACK: type: integer - format: int64 + format: uint64 description: The total number of received ACK (Acknowledgement) control packets packetsSentNAK: type: integer - format: int64 + format: uint64 description: The total number of sent NAK (Negative Acknowledgement) control packets packetsReceivedNAK: type: integer - format: int64 + format: uint64 description: The total number of received NAK (Negative Acknowledgement) control packets packetsSentKM: type: integer - format: int64 + format: uint64 description: The total number of sent KM (Key Material) control packets packetsReceivedKM: type: integer - format: int64 + format: uint64 description: The total number of received KM (Key Material) control packets usSndDuration: type: integer - format: int64 + format: uint64 description: The total accumulated time in microseconds, during which the SRT sender has some data to transmit, including packets that have been sent, but not yet acknowledged packetsSendDrop: type: integer - format: int64 + format: uint64 description: The total number of dropped by the SRT sender DATA packets that have no chance to be delivered in time packetsReceivedDrop: type: integer - format: int64 + format: uint64 description: The total number of dropped by the SRT receiver and, as a result, not delivered to the upstream application DATA packets packetsReceivedUndecrypt: type: integer - format: int64 + format: uint64 description: The total number of packets that failed to be decrypted at the receiver side bytesSent: type: integer - format: int64 + format: uint64 description: Same as packetsSent, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) bytesReceived: type: integer - format: int64 + format: uint64 description: Same as packetsReceived, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) bytesReceivedBelated: type: integer - format: int64 + format: uint64 bytesSentUnique: type: integer - format: int64 + format: uint64 description: Same as packetsSentUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) bytesReceivedUnique: type: integer - format: int64 + format: uint64 description: Same as packetsReceivedUnique, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) bytesReceivedLoss: type: integer - format: int64 + format: uint64 description: Same as packetsReceivedLoss, but expressed in bytes, including payload and all the headers (IP, TCP, SRT), bytes for the presently missing (either reordered or lost) packets' payloads are estimated based on the average packet size bytesRetrans: type: integer - format: int64 + format: uint64 description: Same as packetsRetrans, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) bytesReceivedRetrans: type: integer - format: int64 + format: uint64 description: Same as packetsReceivedRetrans, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) bytesSendDrop: type: integer - format: int64 + format: uint64 description: Same as packetsSendDrop, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) bytesReceivedDrop: type: integer - format: int64 + format: uint64 description: Same as packetsReceivedDrop, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) bytesReceivedUndecrypt: type: integer - format: int64 + format: uint64 description: Same as packetsReceivedUndecrypt, but expressed in bytes, including payload and all the headers (IP, TCP, SRT) usPacketsSendPeriod: type: number @@ -991,11 +987,11 @@ components: description: Current minimum time interval between which consecutive packets are sent, in microseconds packetsFlowWindow: type: integer - format: int64 + format: uint64 description: The maximum number of packets that can be "in flight" packetsFlightSize: type: integer - format: int64 + format: uint64 description: The number of packets in flight msRTT: type: number @@ -1015,11 +1011,11 @@ components: description: Estimated capacity of the network link, in Mbps bytesAvailSendBuf: type: integer - format: int64 + format: uint64 description: The available space in the sender's buffer, in bytes bytesAvailReceiveBuf: type: integer - format: int64 + format: uint64 description: The available space in the receiver's buffer, in bytes mbpsMaxBW: type: number @@ -1027,47 +1023,47 @@ components: description: Transmission bandwidth limit, in Mbps byteMSS: type: integer - format: int64 + format: uint64 description: Maximum Segment Size (MSS), in bytes packetsSendBuf: type: integer - format: int64 + format: uint64 description: The number of packets in the sender's buffer that are already scheduled for sending or even possibly sent, but not yet acknowledged bytesSendBuf: type: integer - format: int64 + format: uint64 description: Instantaneous (current) value of packetsSndBuf, but expressed in bytes, including payload and all headers (IP, TCP, SRT) msSendBuf: type: integer - format: int64 + format: uint64 description: The timespan (msec) of packets in the sender's buffer (unacknowledged packets) msSendTsbPdDelay: type: integer - format: int64 + format: uint64 description: Timestamp-based Packet Delivery Delay value of the peer packetsReceiveBuf: type: integer - format: int64 + format: uint64 description: The number of acknowledged packets in receiver's buffer bytesReceiveBuf: type: integer - format: int64 + format: uint64 description: Instantaneous (current) value of packetsRcvBuf, expressed in bytes, including payload and all headers (IP, TCP, SRT) msReceiveBuf: type: integer - format: int64 + format: uint64 description: The timespan (msec) of acknowledged packets in the receiver's buffer msReceiveTsbPdDelay: type: integer - format: int64 + format: uint64 description: Timestamp-based Packet Delivery Delay value set on the socket via SRTO_RCVLATENCY or SRTO_LATENCY packetsReorderTolerance: type: integer - format: int64 + format: uint64 description: Instant value of the packet reorder tolerance packetsReceivedAvgBelatedTime: type: integer - format: int64 + format: uint64 description: Accumulated difference between the current time and the time-to-play of a packet that is received late packetsSendLossRate: type: number @@ -1092,11 +1088,24 @@ components: items: $ref: '#/components/schemas/SRTConn' + WebRTCICEServer: + type: object + properties: + url: + type: string + username: + type: string + password: + type: string + clientOnly: + type: boolean + WebRTCSession: type: object properties: id: type: string + format: uuid created: type: string remoteAddr: @@ -1116,28 +1125,28 @@ components: type: string bytesReceived: type: integer - format: int64 + format: uint64 bytesSent: type: integer - format: int64 + format: uint64 rtpPacketsReceived: type: integer - format: int64 + format: uint64 rtpPacketsSent: type: integer - format: int64 + format: uint64 rtpPacketsLost: type: integer - format: int64 + format: uint64 rtpPacketsJitter: type: number format: double rtcpPacketsReceived: type: integer - format: int64 + format: uint64 rtcpPacketsSent: type: integer - format: int64 + format: uint64 WebRTCSessionList: type: object diff --git a/internal/api/api.go b/internal/api/api.go index 4dae0691..eefc81df 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -54,10 +54,10 @@ func recordingsOfPath( segments, _ := recordstore.FindSegments(pathConf, pathName, nil, nil) - ret.Segments = make([]*defs.APIRecordingSegment, len(segments)) + ret.Segments = make([]defs.APIRecordingSegment, len(segments)) for i, seg := range segments { - ret.Segments[i] = &defs.APIRecordingSegment{ + ret.Segments[i] = defs.APIRecordingSegment{ Start: seg.Start, } } diff --git a/internal/api/api_config_paths.go b/internal/api/api_config_paths.go index 7aedd064..01b7a2bd 100644 --- a/internal/api/api_config_paths.go +++ b/internal/api/api_config_paths.go @@ -17,11 +17,11 @@ func (a *API) onConfigPathsList(ctx *gin.Context) { a.mutex.RUnlock() data := &defs.APIPathConfList{ - Items: make([]*conf.Path, len(c.Paths)), + Items: make([]conf.Path, len(c.Paths)), } for i, key := range sortedKeys(c.Paths) { - data.Items[i] = c.Paths[key] + data.Items[i] = *c.Paths[key] } data.ItemCount = len(data.Items) diff --git a/internal/api/api_hls_test.go b/internal/api/api_hls_test.go index 72e184bf..a358a1d1 100644 --- a/internal/api/api_hls_test.go +++ b/internal/api/api_hls_test.go @@ -17,9 +17,9 @@ type testHLSServer struct { } func (s *testHLSServer) APIMuxersList() (*defs.APIHLSMuxerList, error) { - items := make([]*defs.APIHLSMuxer, 0, len(s.muxers)) + items := make([]defs.APIHLSMuxer, 0, len(s.muxers)) for _, muxer := range s.muxers { - items = append(items, muxer) + items = append(items, *muxer) } return &defs.APIHLSMuxerList{Items: items}, nil } diff --git a/internal/api/api_paths_test.go b/internal/api/api_paths_test.go index 4576a9c8..61a3683f 100644 --- a/internal/api/api_paths_test.go +++ b/internal/api/api_paths_test.go @@ -16,9 +16,9 @@ type testPathManager struct { } func (m *testPathManager) APIPathsList() (*defs.APIPathList, error) { - items := make([]*defs.APIPath, 0, len(m.paths)) + items := make([]defs.APIPath, 0, len(m.paths)) for _, path := range m.paths { - items = append(items, path) + items = append(items, *path) } return &defs.APIPathList{Items: items}, nil } @@ -38,13 +38,13 @@ func TestPathsList(t *testing.T) { "test1": { Name: "test1", ConfName: "test1", - Source: &defs.APIPathSourceOrReader{Type: "publisher", ID: "pub1"}, + Source: &defs.APIPathSource{Type: "publisher", ID: "pub1"}, Ready: true, ReadyTime: &now, Tracks: []string{"H264", "Opus"}, BytesReceived: 1000, BytesSent: 2000, - Readers: []defs.APIPathSourceOrReader{ + Readers: []defs.APIPathReader{ {Type: "reader", ID: "reader1"}, }, }, @@ -55,7 +55,7 @@ func TestPathsList(t *testing.T) { Tracks: []string{}, BytesReceived: 500, BytesSent: 100, - Readers: []defs.APIPathSourceOrReader{}, + Readers: []defs.APIPathReader{}, }, }, } @@ -91,13 +91,13 @@ func TestPathsGet(t *testing.T) { "mystream": { Name: "mystream", ConfName: "mystream", - Source: &defs.APIPathSourceOrReader{Type: "rtspSession", ID: "session123"}, + Source: &defs.APIPathSource{Type: "rtspSession", ID: "session123"}, Ready: true, ReadyTime: &now, Tracks: []string{"H264", "Opus"}, BytesReceived: 123456, BytesSent: 789012, - Readers: []defs.APIPathSourceOrReader{ + Readers: []defs.APIPathReader{ {Type: "hlsMuxer", ID: "muxer1"}, {Type: "webRTCSession", ID: "session456"}, }, diff --git a/internal/api/api_recordings.go b/internal/api/api_recordings.go index e09a521c..7fb0b946 100644 --- a/internal/api/api_recordings.go +++ b/internal/api/api_recordings.go @@ -30,11 +30,11 @@ func (a *API) onRecordingsList(ctx *gin.Context) { } data.PageCount = pageCount - data.Items = make([]*defs.APIRecording, len(pathNames)) + data.Items = make([]defs.APIRecording, len(pathNames)) for i, pathName := range pathNames { pathConf, _, _ := conf.FindPathConf(c.Paths, pathName) - data.Items[i] = recordingsOfPath(pathConf, pathName) + data.Items[i] = *recordingsOfPath(pathConf, pathName) } ctx.JSON(http.StatusOK, data) diff --git a/internal/api/api_rtmp_test.go b/internal/api/api_rtmp_test.go index dbf78f3f..7cc831f8 100644 --- a/internal/api/api_rtmp_test.go +++ b/internal/api/api_rtmp_test.go @@ -19,9 +19,9 @@ type testRTMPServer struct { } func (s *testRTMPServer) APIConnsList() (*defs.APIRTMPConnList, error) { - items := make([]*defs.APIRTMPConn, 0, len(s.conns)) + items := make([]defs.APIRTMPConn, 0, len(s.conns)) for _, conn := range s.conns { - items = append(items, conn) + items = append(items, *conn) } return &defs.APIRTMPConnList{Items: items}, nil } diff --git a/internal/api/api_rtsp_test.go b/internal/api/api_rtsp_test.go index 33e10584..776d2ee9 100644 --- a/internal/api/api_rtsp_test.go +++ b/internal/api/api_rtsp_test.go @@ -21,9 +21,9 @@ type testRTSPServer struct { } func (s *testRTSPServer) APIConnsList() (*defs.APIRTSPConnsList, error) { - items := make([]*defs.APIRTSPConn, 0, len(s.conns)) + items := make([]defs.APIRTSPConn, 0, len(s.conns)) for _, conn := range s.conns { - items = append(items, conn) + items = append(items, *conn) } return &defs.APIRTSPConnsList{Items: items}, nil } @@ -37,9 +37,9 @@ func (s *testRTSPServer) APIConnsGet(id uuid.UUID) (*defs.APIRTSPConn, error) { } func (s *testRTSPServer) APISessionsList() (*defs.APIRTSPSessionList, error) { - items := make([]*defs.APIRTSPSession, 0, len(s.sessions)) + items := make([]defs.APIRTSPSession, 0, len(s.sessions)) for _, session := range s.sessions { - items = append(items, session) + items = append(items, *session) } return &defs.APIRTSPSessionList{Items: items}, nil } diff --git a/internal/api/api_srt_test.go b/internal/api/api_srt_test.go index 059acbbe..c059b3b0 100644 --- a/internal/api/api_srt_test.go +++ b/internal/api/api_srt_test.go @@ -19,9 +19,9 @@ type testSRTServer struct { } func (s *testSRTServer) APIConnsList() (*defs.APISRTConnList, error) { - items := make([]*defs.APISRTConn, 0, len(s.conns)) + items := make([]defs.APISRTConn, 0, len(s.conns)) for _, conn := range s.conns { - items = append(items, conn) + items = append(items, *conn) } return &defs.APISRTConnList{Items: items}, nil } diff --git a/internal/api/api_webrtc_test.go b/internal/api/api_webrtc_test.go index da547d06..d0111f5e 100644 --- a/internal/api/api_webrtc_test.go +++ b/internal/api/api_webrtc_test.go @@ -19,9 +19,9 @@ type testWebRTCServer struct { } func (s *testWebRTCServer) APISessionsList() (*defs.APIWebRTCSessionList, error) { - items := make([]*defs.APIWebRTCSession, 0, len(s.sessions)) + items := make([]defs.APIWebRTCSession, 0, len(s.sessions)) for _, session := range s.sessions { - items = append(items, session) + items = append(items, *session) } return &defs.APIWebRTCSessionList{Items: items}, nil } diff --git a/internal/core/path.go b/internal/core/path.go index ca586b9c..066187fa 100644 --- a/internal/core/path.go +++ b/internal/core/path.go @@ -550,12 +550,12 @@ func (pa *path) doAPIPathsGet(req pathAPIPathsGetReq) { data: &defs.APIPath{ Name: pa.name, ConfName: pa.conf.Name, - Source: func() *defs.APIPathSourceOrReader { + Source: func() *defs.APIPathSource { if pa.source == nil { return nil } v := pa.source.APISourceDescribe() - return &v + return v }(), Ready: pa.isReady(), ReadyTime: func() *time.Time { @@ -583,11 +583,11 @@ func (pa *path) doAPIPathsGet(req pathAPIPathsGetReq) { } return pa.stream.BytesSent() }(), - Readers: func() []defs.APIPathSourceOrReader { - ret := make([]defs.APIPathSourceOrReader, len(pa.readers)) + Readers: func() []defs.APIPathReader { + ret := make([]defs.APIPathReader, len(pa.readers)) i := 0 for r := range pa.readers { - ret[i] = r.APIReaderDescribe() + ret[i] = *r.APIReaderDescribe() i++ } return ret @@ -713,7 +713,7 @@ func (pa *path) setReady(desc *description.Session, useRTPPackets bool, replaceN ExternalCmdPool: pa.externalCmdPool, Conf: pa.conf, ExternalCmdEnv: pa.ExternalCmdEnv(), - Desc: pa.source.APISourceDescribe(), + Desc: *pa.source.APISourceDescribe(), Query: pa.publisherQuery, }) diff --git a/internal/core/path_manager.go b/internal/core/path_manager.go index 5f928ff4..dce1eb98 100644 --- a/internal/core/path_manager.go +++ b/internal/core/path_manager.go @@ -594,13 +594,13 @@ func (pm *pathManager) APIPathsList() (*defs.APIPathList, error) { res := <-req.res res.data = &defs.APIPathList{ - Items: []*defs.APIPath{}, + Items: []defs.APIPath{}, } for _, pa := range res.paths { item, err := pa.APIPathsGet(pathAPIPathsGetReq{}) if err == nil { - res.data.Items = append(res.data.Items, item) + res.data.Items = append(res.data.Items, *item) } } diff --git a/internal/core/source_redirect.go b/internal/core/source_redirect.go index 99744415..296cf4fd 100644 --- a/internal/core/source_redirect.go +++ b/internal/core/source_redirect.go @@ -12,8 +12,8 @@ func (*sourceRedirect) Log(logger.Level, string, ...any) { } // APISourceDescribe implements source. -func (*sourceRedirect) APISourceDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (*sourceRedirect) APISourceDescribe() *defs.APIPathSource { + return &defs.APIPathSource{ Type: "redirect", ID: "", } diff --git a/internal/defs/api.go b/internal/defs/api.go index 9bc30831..80fe7ac2 100644 --- a/internal/defs/api.go +++ b/internal/defs/api.go @@ -69,35 +69,41 @@ type APIInfo struct { // APIPathConfList is a list of path configurations. type APIPathConfList struct { - ItemCount int `json:"itemCount"` - PageCount int `json:"pageCount"` - Items []*conf.Path `json:"items"` + ItemCount int `json:"itemCount"` + PageCount int `json:"pageCount"` + Items []conf.Path `json:"items"` } -// APIPathSourceOrReader is a source or a reader. -type APIPathSourceOrReader struct { +// APIPathSource is a source. +type APIPathSource struct { + Type string `json:"type"` + ID string `json:"id"` +} + +// APIPathReader is a reader. +type APIPathReader struct { Type string `json:"type"` ID string `json:"id"` } // APIPath is a path. type APIPath struct { - Name string `json:"name"` - ConfName string `json:"confName"` - Source *APIPathSourceOrReader `json:"source"` - Ready bool `json:"ready"` - ReadyTime *time.Time `json:"readyTime"` - Tracks []string `json:"tracks"` - BytesReceived uint64 `json:"bytesReceived"` - BytesSent uint64 `json:"bytesSent"` - Readers []APIPathSourceOrReader `json:"readers"` + Name string `json:"name"` + ConfName string `json:"confName"` + Source *APIPathSource `json:"source"` + Ready bool `json:"ready"` + ReadyTime *time.Time `json:"readyTime"` + Tracks []string `json:"tracks"` + BytesReceived uint64 `json:"bytesReceived"` + BytesSent uint64 `json:"bytesSent"` + Readers []APIPathReader `json:"readers"` } // APIPathList is a list of paths. type APIPathList struct { - ItemCount int `json:"itemCount"` - PageCount int `json:"pageCount"` - Items []*APIPath `json:"items"` + ItemCount int `json:"itemCount"` + PageCount int `json:"pageCount"` + Items []APIPath `json:"items"` } // APIHLSMuxer is an HLS muxer. @@ -110,9 +116,9 @@ type APIHLSMuxer struct { // APIHLSMuxerList is a list of HLS muxers. type APIHLSMuxerList struct { - ItemCount int `json:"itemCount"` - PageCount int `json:"pageCount"` - Items []*APIHLSMuxer `json:"items"` + ItemCount int `json:"itemCount"` + PageCount int `json:"pageCount"` + Items []APIHLSMuxer `json:"items"` } // APIRTMPConnState is the state of a RTMP connection. @@ -139,9 +145,9 @@ type APIRTMPConn struct { // APIRTMPConnList is a list of RTMP connections. type APIRTMPConnList struct { - ItemCount int `json:"itemCount"` - PageCount int `json:"pageCount"` - Items []*APIRTMPConn `json:"items"` + ItemCount int `json:"itemCount"` + PageCount int `json:"pageCount"` + Items []APIRTMPConn `json:"items"` } // APIRTSPConn is a RTSP connection. @@ -157,9 +163,9 @@ type APIRTSPConn struct { // APIRTSPConnsList is a list of RTSP connections. type APIRTSPConnsList struct { - ItemCount int `json:"itemCount"` - PageCount int `json:"pageCount"` - Items []*APIRTSPConn `json:"items"` + ItemCount int `json:"itemCount"` + PageCount int `json:"pageCount"` + Items []APIRTSPConn `json:"items"` } // APIRTSPSessionState is the state of a RTSP session. @@ -196,9 +202,9 @@ type APIRTSPSession struct { // APIRTSPSessionList is a list of RTSP sessions. type APIRTSPSessionList struct { - ItemCount int `json:"itemCount"` - PageCount int `json:"pageCount"` - Items []*APIRTSPSession `json:"items"` + ItemCount int `json:"itemCount"` + PageCount int `json:"pageCount"` + Items []APIRTSPSession `json:"items"` } // APISRTConnState is the state of a SRT connection. @@ -344,9 +350,9 @@ type APISRTConn struct { // APISRTConnList is a list of SRT connections. type APISRTConnList struct { - ItemCount int `json:"itemCount"` - PageCount int `json:"pageCount"` - Items []*APISRTConn `json:"items"` + ItemCount int `json:"itemCount"` + PageCount int `json:"pageCount"` + Items []APISRTConn `json:"items"` } // APIWebRTCSessionState is the state of a WebRTC connection. @@ -381,9 +387,9 @@ type APIWebRTCSession struct { // APIWebRTCSessionList is a list of WebRTC sessions. type APIWebRTCSessionList struct { - ItemCount int `json:"itemCount"` - PageCount int `json:"pageCount"` - Items []*APIWebRTCSession `json:"items"` + ItemCount int `json:"itemCount"` + PageCount int `json:"pageCount"` + Items []APIWebRTCSession `json:"items"` } // APIRecordingSegment is a recording segment. @@ -393,13 +399,13 @@ type APIRecordingSegment struct { // APIRecording is a recording. type APIRecording struct { - Name string `json:"name"` - Segments []*APIRecordingSegment `json:"segments"` + Name string `json:"name"` + Segments []APIRecordingSegment `json:"segments"` } // APIRecordingList is a list of recordings. type APIRecordingList struct { - ItemCount int `json:"itemCount"` - PageCount int `json:"pageCount"` - Items []*APIRecording `json:"items"` + ItemCount int `json:"itemCount"` + PageCount int `json:"pageCount"` + Items []APIRecording `json:"items"` } diff --git a/internal/defs/reader.go b/internal/defs/reader.go index c6f9f640..f41a84c9 100644 --- a/internal/defs/reader.go +++ b/internal/defs/reader.go @@ -3,5 +3,5 @@ package defs // Reader is an entity that can read a stream. type Reader interface { Close() - APIReaderDescribe() APIPathSourceOrReader + APIReaderDescribe() *APIPathReader } diff --git a/internal/defs/source.go b/internal/defs/source.go index 0a0d39e4..997ee48d 100644 --- a/internal/defs/source.go +++ b/internal/defs/source.go @@ -17,7 +17,7 @@ import ( // - core.sourceRedirect type Source interface { logger.Writer - APISourceDescribe() APIPathSourceOrReader + APISourceDescribe() *APIPathSource } // FormatsToCodecs returns the name of codecs of given formats. diff --git a/internal/hooks/on_connect.go b/internal/hooks/on_connect.go index 2e6454ee..1fd99486 100644 --- a/internal/hooks/on_connect.go +++ b/internal/hooks/on_connect.go @@ -16,7 +16,7 @@ type OnConnectParams struct { RunOnConnectRestart bool RunOnDisconnect string RTSPAddress string - Desc defs.APIPathSourceOrReader + Desc defs.APIPathReader } // OnConnect is the OnConnect hook. diff --git a/internal/hooks/on_read.go b/internal/hooks/on_read.go index ee272066..7a5cf261 100644 --- a/internal/hooks/on_read.go +++ b/internal/hooks/on_read.go @@ -13,7 +13,7 @@ type OnReadParams struct { ExternalCmdPool *externalcmd.Pool Conf *conf.Path ExternalCmdEnv externalcmd.Environment - Reader defs.APIPathSourceOrReader + Reader defs.APIPathReader Query string } diff --git a/internal/hooks/on_ready.go b/internal/hooks/on_ready.go index f6e7e9fd..f125f0a9 100644 --- a/internal/hooks/on_ready.go +++ b/internal/hooks/on_ready.go @@ -13,7 +13,7 @@ type OnReadyParams struct { ExternalCmdPool *externalcmd.Pool Conf *conf.Path ExternalCmdEnv externalcmd.Environment - Desc defs.APIPathSourceOrReader + Desc defs.APIPathSource Query string } diff --git a/internal/metrics/metrics_test.go b/internal/metrics/metrics_test.go index 9be2c982..277ba740 100644 --- a/internal/metrics/metrics_test.go +++ b/internal/metrics/metrics_test.go @@ -26,10 +26,10 @@ func (dummyPathManager) APIPathsList() (*defs.APIPathList, error) { return &defs.APIPathList{ ItemCount: 1, PageCount: 1, - Items: []*defs.APIPath{{ + Items: []defs.APIPath{{ Name: "mypath", ConfName: "mypathconf", - Source: &defs.APIPathSourceOrReader{ + Source: &defs.APIPathSource{ Type: "testing", ID: "123324354", }, @@ -38,7 +38,7 @@ func (dummyPathManager) APIPathsList() (*defs.APIPathList, error) { Tracks: []string{"H264", "H265"}, BytesReceived: 123, BytesSent: 456, - Readers: []defs.APIPathSourceOrReader{ + Readers: []defs.APIPathReader{ { Type: "testing", ID: "345234423", @@ -58,7 +58,7 @@ func (dummyHLSServer) APIMuxersList() (*defs.APIHLSMuxerList, error) { return &defs.APIHLSMuxerList{ ItemCount: 1, PageCount: 1, - Items: []*defs.APIHLSMuxer{{ + Items: []defs.APIHLSMuxer{{ Path: "mypath", Created: time.Date(2003, 11, 4, 23, 15, 7, 0, time.UTC), LastRequest: time.Date(2003, 11, 4, 23, 15, 7, 0, time.UTC), @@ -77,7 +77,7 @@ func (dummyRTSPServer) APIConnsList() (*defs.APIRTSPConnsList, error) { return &defs.APIRTSPConnsList{ ItemCount: 1, PageCount: 1, - Items: []*defs.APIRTSPConn{{ + Items: []defs.APIRTSPConn{{ ID: uuid.MustParse("18294761-f9d1-4ea9-9a35-fe265b62eb41"), Created: time.Date(2003, 11, 4, 23, 15, 7, 0, time.UTC), RemoteAddr: "124.5.5.5:34542", @@ -96,7 +96,7 @@ func (dummyRTSPServer) APISessionsList() (*defs.APIRTSPSessionList, error) { return &defs.APIRTSPSessionList{ ItemCount: 1, PageCount: 1, - Items: []*defs.APIRTSPSession{{ + Items: []defs.APIRTSPSession{{ ID: uuid.MustParse("124b22ce-9c34-4387-b045-44caf98049f7"), Created: time.Date(2003, 11, 4, 23, 15, 7, 0, time.UTC), RemoteAddr: "124.5.5.5:34542", @@ -132,7 +132,7 @@ func (dummyRTMPServer) APIConnsList() (*defs.APIRTMPConnList, error) { return &defs.APIRTMPConnList{ ItemCount: 1, PageCount: 1, - Items: []*defs.APIRTMPConn{{ + Items: []defs.APIRTMPConn{{ ID: uuid.MustParse("9a07afe4-fc07-4c9b-be6e-6255720c36d0"), Created: time.Date(2003, 11, 4, 23, 15, 7, 0, time.UTC), RemoteAddr: "3.3.3.3:5678", @@ -159,7 +159,7 @@ func (dummyWebRTCServer) APISessionsList() (*defs.APIWebRTCSessionList, error) { return &defs.APIWebRTCSessionList{ ItemCount: 1, PageCount: 1, - Items: []*defs.APIWebRTCSession{{ + Items: []defs.APIWebRTCSession{{ ID: uuid.MustParse("f47ac10b-58cc-4372-a567-0e02b2c3d479"), Created: time.Date(2003, 11, 4, 23, 15, 7, 0, time.UTC), RemoteAddr: "127.0.0.1:3455", diff --git a/internal/servers/hls/muxer.go b/internal/servers/hls/muxer.go index 122f5546..cdb8aa5b 100644 --- a/internal/servers/hls/muxer.go +++ b/internal/servers/hls/muxer.go @@ -245,8 +245,8 @@ func (m *muxer) getInstance() *muxerInstance { } // APIReaderDescribe implements reader. -func (m *muxer) APIReaderDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (m *muxer) APIReaderDescribe() *defs.APIPathReader { + return &defs.APIPathReader{ Type: "hlsMuxer", ID: "", } diff --git a/internal/servers/hls/server.go b/internal/servers/hls/server.go index 272d45c7..180ea1cb 100644 --- a/internal/servers/hls/server.go +++ b/internal/servers/hls/server.go @@ -217,11 +217,11 @@ outer: case req := <-s.chAPIMuxerList: data := &defs.APIHLSMuxerList{ - Items: []*defs.APIHLSMuxer{}, + Items: []defs.APIHLSMuxer{}, } for _, muxer := range s.muxers { - data.Items = append(data.Items, muxer.apiItem()) + data.Items = append(data.Items, *muxer.apiItem()) } sort.Slice(data.Items, func(i, j int) bool { diff --git a/internal/servers/rtmp/conn.go b/internal/servers/rtmp/conn.go index ddcc4997..6c675289 100644 --- a/internal/servers/rtmp/conn.go +++ b/internal/servers/rtmp/conn.go @@ -89,7 +89,7 @@ func (c *conn) run() { //nolint:dupl RunOnConnectRestart: c.runOnConnectRestart, RunOnDisconnect: c.runOnDisconnect, RTSPAddress: c.rtspAddress, - Desc: c.APIReaderDescribe(), + Desc: *c.APIReaderDescribe(), }) defer onDisconnectHook() @@ -198,7 +198,7 @@ func (c *conn) runRead() error { ExternalCmdPool: c.externalCmdPool, Conf: path.SafeConf(), ExternalCmdEnv: path.ExternalCmdEnv(), - Reader: c.APISourceDescribe(), + Reader: *c.APIReaderDescribe(), Query: c.rconn.URL.RawQuery, }) defer onUnreadHook() @@ -285,8 +285,8 @@ func (c *conn) runPublish() error { } // APIReaderDescribe implements reader. -func (c *conn) APIReaderDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (c *conn) APIReaderDescribe() *defs.APIPathReader { + return &defs.APIPathReader{ Type: func() string { if c.isTLS { return "rtmpsConn" @@ -298,8 +298,16 @@ func (c *conn) APIReaderDescribe() defs.APIPathSourceOrReader { } // APISourceDescribe implements source. -func (c *conn) APISourceDescribe() defs.APIPathSourceOrReader { - return c.APIReaderDescribe() +func (c *conn) APISourceDescribe() *defs.APIPathSource { + return &defs.APIPathSource{ + Type: func() string { + if c.isTLS { + return "rtmpsConn" + } + return "rtmpConn" + }(), + ID: c.uuid.String(), + } } func (c *conn) apiItem() *defs.APIRTMPConn { diff --git a/internal/servers/rtmp/server.go b/internal/servers/rtmp/server.go index 09088661..6a16acb7 100644 --- a/internal/servers/rtmp/server.go +++ b/internal/servers/rtmp/server.go @@ -227,11 +227,11 @@ outer: case req := <-s.chAPIConnsList: data := &defs.APIRTMPConnList{ - Items: []*defs.APIRTMPConn{}, + Items: []defs.APIRTMPConn{}, } for c := range s.conns { - data.Items = append(data.Items, c.apiItem()) + data.Items = append(data.Items, *c.apiItem()) } sort.Slice(data.Items, func(i, j int) bool { diff --git a/internal/servers/rtsp/conn.go b/internal/servers/rtsp/conn.go index b6b85b6e..7992c8b8 100644 --- a/internal/servers/rtsp/conn.go +++ b/internal/servers/rtsp/conn.go @@ -78,16 +78,6 @@ func (c *conn) initialize() { c.Log(logger.Info, "opened") - desc := defs.APIPathSourceOrReader{ - Type: func() string { - if c.isTLS { - return "rtspsConn" - } - return "rtspConn" - }(), - ID: c.uuid.String(), - } - c.onDisconnectHook = hooks.OnConnect(hooks.OnConnectParams{ Logger: c, ExternalCmdPool: c.externalCmdPool, @@ -95,7 +85,15 @@ func (c *conn) initialize() { RunOnConnectRestart: c.runOnConnectRestart, RunOnDisconnect: c.runOnDisconnect, RTSPAddress: c.rtspAddress, - Desc: desc, + Desc: defs.APIPathReader{ + Type: func() string { + if c.isTLS { + return "rtspsConn" + } + return "rtspConn" + }(), + ID: c.uuid.String(), + }, }) } diff --git a/internal/servers/rtsp/server.go b/internal/servers/rtsp/server.go index 60b383b3..4c583cd9 100644 --- a/internal/servers/rtsp/server.go +++ b/internal/servers/rtsp/server.go @@ -407,11 +407,11 @@ func (s *Server) APIConnsList() (*defs.APIRTSPConnsList, error) { defer s.mutex.RUnlock() data := &defs.APIRTSPConnsList{ - Items: []*defs.APIRTSPConn{}, + Items: []defs.APIRTSPConn{}, } for _, c := range s.conns { - data.Items = append(data.Items, c.apiItem()) + data.Items = append(data.Items, *c.apiItem()) } sort.Slice(data.Items, func(i, j int) bool { @@ -452,11 +452,11 @@ func (s *Server) APISessionsList() (*defs.APIRTSPSessionList, error) { defer s.mutex.RUnlock() data := &defs.APIRTSPSessionList{ - Items: []*defs.APIRTSPSession{}, + Items: []defs.APIRTSPSession{}, } for _, s := range s.sessions { - data.Items = append(data.Items, s.apiItem()) + data.Items = append(data.Items, *s.apiItem()) } sort.Slice(data.Items, func(i, j int) bool { diff --git a/internal/servers/rtsp/session.go b/internal/servers/rtsp/session.go index 78e97c76..b634ed77 100644 --- a/internal/servers/rtsp/session.go +++ b/internal/servers/rtsp/session.go @@ -297,7 +297,7 @@ func (s *session) onPlay(_ *gortsplib.ServerHandlerOnPlayCtx) (*base.Response, e ExternalCmdPool: s.externalCmdPool, Conf: s.path.SafeConf(), ExternalCmdEnv: s.path.ExternalCmdEnv(), - Reader: s.APIReaderDescribe(), + Reader: *s.APIReaderDescribe(), Query: s.rsession.Query(), }) } @@ -360,8 +360,8 @@ func (s *session) onPause(_ *gortsplib.ServerHandlerOnPauseCtx) (*base.Response, } // APIReaderDescribe implements reader. -func (s *session) APIReaderDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (s *session) APIReaderDescribe() *defs.APIPathReader { + return &defs.APIPathReader{ Type: func() string { if s.isTLS { return "rtspsSession" @@ -373,8 +373,16 @@ func (s *session) APIReaderDescribe() defs.APIPathSourceOrReader { } // APISourceDescribe implements source. -func (s *session) APISourceDescribe() defs.APIPathSourceOrReader { - return s.APIReaderDescribe() +func (s *session) APISourceDescribe() *defs.APIPathSource { + return &defs.APIPathSource{ + Type: func() string { + if s.isTLS { + return "rtspsSession" + } + return "rtspSession" + }(), + ID: s.uuid.String(), + } } // onPacketLost is called by rtspServer. diff --git a/internal/servers/srt/conn.go b/internal/servers/srt/conn.go index bf56b2b7..d0696e1b 100644 --- a/internal/servers/srt/conn.go +++ b/internal/servers/srt/conn.go @@ -103,7 +103,7 @@ func (c *conn) run() { //nolint:dupl RunOnConnectRestart: c.runOnConnectRestart, RunOnDisconnect: c.runOnDisconnect, RTSPAddress: c.rtspAddress, - Desc: c.APIReaderDescribe(), + Desc: *c.APIReaderDescribe(), }) defer onDisconnectHook() @@ -317,7 +317,7 @@ func (c *conn) runRead(streamID *streamID) error { ExternalCmdPool: c.externalCmdPool, Conf: path.SafeConf(), ExternalCmdEnv: path.ExternalCmdEnv(), - Reader: c.APIReaderDescribe(), + Reader: *c.APIReaderDescribe(), Query: streamID.query, }) defer onUnreadHook() @@ -338,16 +338,19 @@ func (c *conn) runRead(streamID *streamID) error { } // APIReaderDescribe implements reader. -func (c *conn) APIReaderDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (c *conn) APIReaderDescribe() *defs.APIPathReader { + return &defs.APIPathReader{ Type: "srtConn", ID: c.uuid.String(), } } // APISourceDescribe implements source. -func (c *conn) APISourceDescribe() defs.APIPathSourceOrReader { - return c.APIReaderDescribe() +func (c *conn) APISourceDescribe() *defs.APIPathSource { + return &defs.APIPathSource{ + Type: "srtConn", + ID: c.uuid.String(), + } } func (c *conn) apiItem() *defs.APISRTConn { diff --git a/internal/servers/srt/server.go b/internal/servers/srt/server.go index 2180b718..2001941f 100644 --- a/internal/servers/srt/server.go +++ b/internal/servers/srt/server.go @@ -195,11 +195,11 @@ outer: case req := <-s.chAPIConnsList: data := &defs.APISRTConnList{ - Items: []*defs.APISRTConn{}, + Items: []defs.APISRTConn{}, } for c := range s.conns { - data.Items = append(data.Items, c.apiItem()) + data.Items = append(data.Items, *c.apiItem()) } sort.Slice(data.Items, func(i, j int) bool { diff --git a/internal/servers/webrtc/server.go b/internal/servers/webrtc/server.go index 6cd812a6..cdfa68aa 100644 --- a/internal/servers/webrtc/server.go +++ b/internal/servers/webrtc/server.go @@ -399,11 +399,11 @@ outer: case req := <-s.chAPISessionsList: data := &defs.APIWebRTCSessionList{ - Items: []*defs.APIWebRTCSession{}, + Items: []defs.APIWebRTCSession{}, } for sx := range s.sessions { - data.Items = append(data.Items, sx.apiItem()) + data.Items = append(data.Items, *sx.apiItem()) } sort.Slice(data.Items, func(i, j int) bool { diff --git a/internal/servers/webrtc/session.go b/internal/servers/webrtc/session.go index 68f91c9f..26016a04 100644 --- a/internal/servers/webrtc/session.go +++ b/internal/servers/webrtc/session.go @@ -373,7 +373,7 @@ func (s *session) runRead() (int, error) { ExternalCmdPool: s.externalCmdPool, Conf: path.SafeConf(), ExternalCmdEnv: path.ExternalCmdEnv(), - Reader: s.APIReaderDescribe(), + Reader: *s.APIReaderDescribe(), Query: s.req.httpRequest.URL.RawQuery, }) defer onUnreadHook() @@ -443,16 +443,19 @@ func (s *session) addCandidates( } // APIReaderDescribe implements reader. -func (s *session) APIReaderDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (s *session) APIReaderDescribe() *defs.APIPathReader { + return &defs.APIPathReader{ Type: "webRTCSession", ID: s.uuid.String(), } } // APISourceDescribe implements source. -func (s *session) APISourceDescribe() defs.APIPathSourceOrReader { - return s.APIReaderDescribe() +func (s *session) APISourceDescribe() *defs.APIPathSource { + return &defs.APIPathSource{ + Type: "webRTCSession", + ID: s.uuid.String(), + } } func (s *session) apiItem() *defs.APIWebRTCSession { diff --git a/internal/staticsources/handler.go b/internal/staticsources/handler.go index 49f7e698..75dca01d 100644 --- a/internal/staticsources/handler.go +++ b/internal/staticsources/handler.go @@ -47,7 +47,7 @@ func resolveSource(s string, matches []string, query string) string { type staticSource interface { logger.Writer Run(defs.StaticSourceRunParams) error - APISourceDescribe() defs.APIPathSourceOrReader + APISourceDescribe() *defs.APIPathSource } type handlerPathManager interface { @@ -300,7 +300,7 @@ func (s *Handler) ReloadConf(newConf *conf.Path) { } // APISourceDescribe instanceements source. -func (s *Handler) APISourceDescribe() defs.APIPathSourceOrReader { +func (s *Handler) APISourceDescribe() *defs.APIPathSource { return s.instance.APISourceDescribe() } diff --git a/internal/staticsources/hls/source.go b/internal/staticsources/hls/source.go index b4c3d0ac..eafa7ac2 100644 --- a/internal/staticsources/hls/source.go +++ b/internal/staticsources/hls/source.go @@ -138,8 +138,8 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error { } // APISourceDescribe implements StaticSource. -func (*Source) APISourceDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (*Source) APISourceDescribe() *defs.APIPathSource { + return &defs.APIPathSource{ Type: "hlsSource", ID: "", } diff --git a/internal/staticsources/mpegts/source.go b/internal/staticsources/mpegts/source.go index c94ef84e..d9812fc6 100644 --- a/internal/staticsources/mpegts/source.go +++ b/internal/staticsources/mpegts/source.go @@ -143,8 +143,8 @@ func (s *Source) runReader(nc net.Conn) error { } // APISourceDescribe implements StaticSource. -func (*Source) APISourceDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (*Source) APISourceDescribe() *defs.APIPathSource { + return &defs.APIPathSource{ Type: "mpegtsSource", ID: "", } diff --git a/internal/staticsources/rpicamera/source.go b/internal/staticsources/rpicamera/source.go index ffab452a..71bd1d06 100644 --- a/internal/staticsources/rpicamera/source.go +++ b/internal/staticsources/rpicamera/source.go @@ -93,8 +93,8 @@ func (r *secondaryReader) Close() { } // APIReaderDescribe implements reader. -func (*secondaryReader) APIReaderDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (*secondaryReader) APIReaderDescribe() *defs.APIPathReader { + return &defs.APIPathReader{ Type: "rpiCameraSecondary", ID: "", } @@ -359,8 +359,8 @@ func (s *Source) waitForPrimary( } // APISourceDescribe implements StaticSource. -func (*Source) APISourceDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (*Source) APISourceDescribe() *defs.APIPathSource { + return &defs.APIPathSource{ Type: "rpiCameraSource", ID: "", } diff --git a/internal/staticsources/rtmp/source.go b/internal/staticsources/rtmp/source.go index 03219053..630bd05d 100644 --- a/internal/staticsources/rtmp/source.go +++ b/internal/staticsources/rtmp/source.go @@ -137,8 +137,8 @@ func (s *Source) runReader(conn *gortmplib.Client) error { } // APISourceDescribe implements StaticSource. -func (*Source) APISourceDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (*Source) APISourceDescribe() *defs.APIPathSource { + return &defs.APIPathSource{ Type: "rtmpSource", ID: "", } diff --git a/internal/staticsources/rtp/source.go b/internal/staticsources/rtp/source.go index 40fc308e..547b998c 100644 --- a/internal/staticsources/rtp/source.go +++ b/internal/staticsources/rtp/source.go @@ -217,8 +217,8 @@ func (s *Source) runReader(desc *description.Session, nc net.Conn) error { } // APISourceDescribe implements StaticSource. -func (*Source) APISourceDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (*Source) APISourceDescribe() *defs.APIPathSource { + return &defs.APIPathSource{ Type: "rtpSource", ID: "", } diff --git a/internal/staticsources/rtsp/source.go b/internal/staticsources/rtsp/source.go index cb03c843..2186d972 100644 --- a/internal/staticsources/rtsp/source.go +++ b/internal/staticsources/rtsp/source.go @@ -270,8 +270,8 @@ func (s *Source) runInner(c *gortsplib.Client, u *base.URL, pathConf *conf.Path) } // APISourceDescribe implements StaticSource. -func (*Source) APISourceDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (*Source) APISourceDescribe() *defs.APIPathSource { + return &defs.APIPathSource{ Type: "rtspSource", ID: "", } diff --git a/internal/staticsources/srt/source.go b/internal/staticsources/srt/source.go index 4545e30e..08f19dd6 100644 --- a/internal/staticsources/srt/source.go +++ b/internal/staticsources/srt/source.go @@ -128,8 +128,8 @@ func (s *Source) runReader(sconn srt.Conn) error { } // APISourceDescribe implements StaticSource. -func (*Source) APISourceDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (*Source) APISourceDescribe() *defs.APIPathSource { + return &defs.APIPathSource{ Type: "srtSource", ID: "", } diff --git a/internal/staticsources/webrtc/source.go b/internal/staticsources/webrtc/source.go index fb216dad..067e1c9c 100644 --- a/internal/staticsources/webrtc/source.go +++ b/internal/staticsources/webrtc/source.go @@ -114,8 +114,8 @@ func (s *Source) Run(params defs.StaticSourceRunParams) error { } // APISourceDescribe implements StaticSource. -func (*Source) APISourceDescribe() defs.APIPathSourceOrReader { - return defs.APIPathSourceOrReader{ +func (*Source) APISourceDescribe() *defs.APIPathSource { + return &defs.APIPathSource{ Type: "webRTCSource", ID: "", } diff --git a/internal/testapidocs/apidocs_test.go b/internal/testapidocs/apidocs_test.go index c0704d39..b7d000dc 100644 --- a/internal/testapidocs/apidocs_test.go +++ b/internal/testapidocs/apidocs_test.go @@ -1,15 +1,19 @@ package main import ( + "net" "os" "reflect" "strings" "testing" "time" + "github.com/bluenviron/gortsplib/v5/pkg/auth" "github.com/bluenviron/mediamtx/internal/conf" "github.com/bluenviron/mediamtx/internal/conf/yamlwrapper" "github.com/bluenviron/mediamtx/internal/defs" + "github.com/bluenviron/mediamtx/internal/logger" + "github.com/google/uuid" "github.com/stretchr/testify/require" ) @@ -32,6 +36,98 @@ type openAPI struct { } `json:"components"` } +func fillProperty(t *testing.T, rt reflect.Type, existing openAPIProperty) openAPIProperty { + switch { + case rt == reflect.TypeOf(""): + return openAPIProperty{Type: "string"} + + case rt == reflect.PointerTo(reflect.TypeOf("")): + return openAPIProperty{ + Type: "string", + Nullable: true, + } + + case rt == reflect.TypeOf(int(0)): + return openAPIProperty{Type: "integer", Format: "int64"} + + case rt == reflect.TypeOf(uint(0)): + return openAPIProperty{Type: "integer", Format: "uint64"} + + case rt == reflect.TypeOf(uint64(0)): + return openAPIProperty{Type: "integer", Format: "uint64"} + + case rt == reflect.TypeOf(float64(0)): + return openAPIProperty{Type: "number", Format: "double"} + + case rt == reflect.TypeOf(false): + return openAPIProperty{Type: "boolean"} + + case rt == reflect.TypeOf(&time.Time{}): + return openAPIProperty{Type: "string", Nullable: true} + + case rt == reflect.TypeOf(uuid.UUID{}): + return openAPIProperty{Type: "string", Format: "uuid"} + + case rt == reflect.PointerTo(reflect.TypeOf(uuid.UUID{})): + return openAPIProperty{Type: "string", Format: "uuid", Nullable: true} + + case rt == reflect.TypeOf(time.Time{}) || + rt == reflect.TypeOf(conf.Duration(0)) || + rt == reflect.TypeOf(net.IPNet{}) || + rt == reflect.TypeOf(conf.Credential("")) || + rt == reflect.TypeOf(conf.RecordFormat(0)) || + rt == reflect.TypeOf(conf.AuthAction("")) || + rt == reflect.TypeOf(conf.Encryption(0)) || + rt == reflect.TypeOf(conf.RTSPTransport{}) || + rt == reflect.TypeOf(conf.StringSize(0)) || + rt == reflect.TypeOf(conf.RTSPRangeType(0)) || + rt == reflect.TypeOf(conf.LogLevel(0)) || + rt == reflect.TypeOf(conf.AuthMethod(0)) || + rt == reflect.TypeOf(logger.Destination(0)) || + rt == reflect.TypeOf(auth.VerifyMethod(0)) || + rt == reflect.TypeOf(conf.HLSVariant(0)) || + rt == reflect.TypeOf(defs.APIRTMPConnState("")) || + rt == reflect.TypeOf(defs.APIWebRTCSessionState("")) || + rt == reflect.TypeOf(defs.APISRTConnState("")) || + rt == reflect.TypeOf(defs.APIRTSPSessionState("")): + return openAPIProperty{Type: "string"} + + case rt == reflect.TypeOf(conf.RTSPTransports{}): + return openAPIProperty{ + Type: "array", + Items: &openAPIProperty{ + Type: "string", + }, + } + + case rt.Kind() == reflect.Struct: + schemaName := strings.TrimPrefix(rt.Name(), "API") + if rt.PkgPath() == "github.com/bluenviron/mediamtx/internal/conf" && schemaName == "Path" { + schemaName = "PathConf" + } + + return openAPIProperty{ + Ref: "#/components/schemas/" + schemaName, + } + + case rt.Kind() == reflect.Pointer && rt.Elem().Kind() == reflect.Struct: + prop := fillProperty(t, rt.Elem(), existing) + prop.Nullable = true + return prop + + case rt.Kind() == reflect.Slice: + items := fillProperty(t, rt.Elem(), *existing.Items) + return openAPIProperty{ + Type: "array", + Items: &items, + } + + default: + t.Errorf("unhandled type: %v", rt) + return openAPIProperty{} + } +} + func TestAPIDocs(t *testing.T) { byts, err := os.ReadFile("../../api/openapi.yaml") require.NoError(t, err) @@ -78,11 +174,11 @@ func TestAPIDocs(t *testing.T) { }, { "PathSource", - defs.APIPathSourceOrReader{}, + defs.APIPathSource{}, }, { "PathReader", - defs.APIPathSourceOrReader{}, + defs.APIPathReader{}, }, { "HLSMuxer", @@ -152,54 +248,15 @@ func TestAPIDocs(t *testing.T) { Type: "object", Properties: make(map[string]openAPIProperty), } + ty := reflect.TypeOf(ca.goStruct) + for i := range ty.NumField() { sf := ty.Field(i) js := sf.Tag.Get("json") + if js != "-" && js != "paths" && js != "pathDefaults" && !strings.Contains(js, ",omitempty") { - switch { - case sf.Type == reflect.TypeOf(""): - content2.Properties[js] = openAPIProperty{Type: "string"} - - case sf.Type == reflect.PointerTo(reflect.TypeOf("")): - content2.Properties[js] = openAPIProperty{ - Type: "string", - Nullable: true, - } - - case sf.Type == reflect.TypeOf(int(0)): - content2.Properties[js] = openAPIProperty{Type: "integer", Format: "int64"} - - case sf.Type == reflect.TypeOf(float64(0)): - content2.Properties[js] = openAPIProperty{Type: "number", Format: "double"} - - case sf.Type == reflect.TypeOf(false): - content2.Properties[js] = openAPIProperty{Type: "boolean"} - - case sf.Type == reflect.TypeOf(time.Time{}): - content2.Properties[js] = openAPIProperty{Type: "string"} - - case sf.Type == reflect.TypeOf(&time.Time{}): - content2.Properties[js] = openAPIProperty{ - Type: "string", - Nullable: true, - } - - case sf.Type == reflect.TypeOf(conf.AuthInternalUserPermissions{}): - content2.Properties[js] = openAPIProperty{ - Type: "array", - Items: &openAPIProperty{ - Ref: "#/components/schemas/AuthInternalUserPermission", - }, - } - - default: - if existing, ok := content1.Properties[js]; ok { - content2.Properties[js] = existing - } else { - t.Errorf("missing item: '%s'", js) - } - } + content2.Properties[js] = fillProperty(t, sf.Type, content1.Properties[js]) } }