diff --git a/src/zcl_abaplint_backend.clas.abap b/src/zcl_abaplint_backend.clas.abap index 479bd162..d9f77730 100644 --- a/src/zcl_abaplint_backend.clas.abap +++ b/src/zcl_abaplint_backend.clas.abap @@ -67,9 +67,20 @@ CLASS zcl_abaplint_backend DEFINITION !iv_object_name TYPE sobj_name RETURNING VALUE(rv_files) TYPE string . + METHODS send_get + IMPORTING + !ii_client TYPE REF TO if_http_client + RAISING + zcx_abaplint_error . + METHODS send_post + IMPORTING + !ii_client TYPE REF TO if_http_client + RAISING + zcx_abaplint_error . METHODS send IMPORTING !ii_client TYPE REF TO if_http_client + !iv_method TYPE string RAISING zcx_abaplint_error . METHODS create_client @@ -194,7 +205,7 @@ CLASS ZCL_ABAPLINT_BACKEND IMPLEMENTATION. li_client->request->set_cdata( lv_cdata ). - send( li_client ). + send_post( li_client ). DATA lv_response TYPE string. lv_response = li_client->response->get_cdata( ). @@ -202,6 +213,20 @@ CLASS ZCL_ABAPLINT_BACKEND IMPLEMENTATION. DATA li_reader TYPE REF TO zif_abaplint_json_reader. li_reader = zcl_abaplint_json_reader=>parse( lv_response ). + IF li_reader->exists( '/success' ) = abap_false. + RAISE EXCEPTION TYPE zcx_abaplint_error + EXPORTING + message = |Unexpected API response shape|. + ENDIF. + + IF li_reader->value_integer( '/success' ) <> 1. + RAISE EXCEPTION TYPE zcx_abaplint_error + EXPORTING + message = |API request failed: { li_reader->value_string( '/error/message' ) }|. + ENDIF. + + li_reader = li_reader->sub_section( '/payload' ). + DATA lt_issues TYPE string_table. DATA lv_issue LIKE LINE OF lt_issues. DATA lv_prefix TYPE string. @@ -280,7 +305,7 @@ CLASS ZCL_ABAPLINT_BACKEND IMPLEMENTATION. DATA lx_error TYPE REF TO zcx_abaplint_error. TRY. - send( li_client ). + send_get( li_client ). rs_message-message = li_client->response->get_cdata( ). rs_message-error = abap_false. CATCH zcx_abaplint_error INTO lx_error. @@ -295,21 +320,29 @@ CLASS ZCL_ABAPLINT_BACKEND IMPLEMENTATION. METHOD send. + ii_client->request->set_method( iv_method ). ii_client->request->set_header_field( name = 'content-type' value = 'application/json' ). - ii_client->request->set_header_field( - name = '~request_method' - value = 'POST' ). - ii_client->send( timeout = 6000 ). - - ii_client->receive( + ii_client->send( + EXPORTING + timeout = 6000 EXCEPTIONS http_communication_failure = 1 http_invalid_state = 2 http_processing_failed = 3 - OTHERS = 4 ). + http_invalid_timeout = 4 + OTHERS = 5 ). + IF sy-subrc = 0. + ii_client->receive( + EXCEPTIONS + http_communication_failure = 1 + http_invalid_state = 2 + http_processing_failed = 3 + OTHERS = 4 ). + ENDIF. + IF sy-subrc <> 0. DATA lv_ecode TYPE i. DATA lv_emessage TYPE string. @@ -329,7 +362,7 @@ CLASS ZCL_ABAPLINT_BACKEND IMPLEMENTATION. IMPORTING code = lv_scode reason = lv_sreason ). - IF lv_scode <> 200. + IF lv_scode < 200 OR lv_scode >= 300. DATA lv_error_response TYPE string. lv_error_response = ii_client->response->get_cdata( ). " good for debugging @@ -339,4 +372,18 @@ CLASS ZCL_ABAPLINT_BACKEND IMPLEMENTATION. ENDIF. ENDMETHOD. + + + METHOD send_get. + send( + ii_client = ii_client + iv_method = if_http_request=>co_request_method_get ). + ENDMETHOD. + + + METHOD send_post. + send( + ii_client = ii_client + iv_method = if_http_request=>co_request_method_post ). + ENDMETHOD. ENDCLASS. diff --git a/src/zcl_abaplint_json_reader.clas.abap b/src/zcl_abaplint_json_reader.clas.abap index 9afa34cd..4e3ca6b9 100644 --- a/src/zcl_abaplint_json_reader.clas.abap +++ b/src/zcl_abaplint_json_reader.clas.abap @@ -1,6 +1,6 @@ CLASS zcl_abaplint_json_reader DEFINITION PUBLIC - CREATE PUBLIC . + CREATE PRIVATE . PUBLIC SECTION. @@ -14,12 +14,6 @@ CLASS zcl_abaplint_json_reader DEFINITION RAISING zcx_abaplint_error. - METHODS constructor - IMPORTING - iv_json TYPE string - RAISING - zcx_abaplint_error. - PROTECTED SECTION. PRIVATE SECTION. @@ -55,15 +49,6 @@ ENDCLASS. CLASS ZCL_ABAPLINT_JSON_READER IMPLEMENTATION. - METHOD constructor. - - DATA lo_parser TYPE REF TO lcl_json_parser. - CREATE OBJECT lo_parser. - mt_json_tree = lo_parser->parse( iv_json ). - - ENDMETHOD. - - METHOD get_item. FIELD-SYMBOLS LIKE LINE OF mt_json_tree. @@ -100,7 +85,11 @@ CLASS ZCL_ABAPLINT_JSON_READER IMPLEMENTATION. METHOD parse. - CREATE OBJECT ro_instance EXPORTING iv_json = iv_json. + DATA lo_parser TYPE REF TO lcl_json_parser. + + CREATE OBJECT ro_instance. + CREATE OBJECT lo_parser. + ro_instance->mt_json_tree = lo_parser->parse( iv_json ). ENDMETHOD. @@ -108,15 +97,26 @@ CLASS ZCL_ABAPLINT_JSON_READER IMPLEMENTATION. METHOD split_path. DATA lv_offs TYPE i. + DATA lv_len TYPE i. + DATA lv_trim_slash TYPE i. - lv_offs = find( val = reverse( iv_path ) sub = '/' ). + lv_len = strlen( iv_path ). + IF lv_len = 0 OR iv_path = '/'. + RETURN. " empty path is the alias for root item = '' + '' + ENDIF. + + IF substring( val = iv_path off = lv_len - 1 ) = '/'. + lv_trim_slash = 1. " ignore last '/' + ENDIF. + + lv_offs = find( val = reverse( iv_path ) sub = '/' off = lv_trim_slash ). IF lv_offs = -1. - lv_offs = 0. + lv_offs = lv_len. " treat whole string as the 'name' part ENDIF. - lv_offs = strlen( iv_path ) - lv_offs. + lv_offs = lv_len - lv_offs. rv_path_name-path = normalize_path( substring( val = iv_path len = lv_offs ) ). - rv_path_name-name = substring( val = iv_path off = lv_offs ). + rv_path_name-name = substring( val = iv_path off = lv_offs len = lv_len - lv_offs - lv_trim_slash ). ENDMETHOD. @@ -146,6 +146,35 @@ CLASS ZCL_ABAPLINT_JSON_READER IMPLEMENTATION. ENDMETHOD. + METHOD zif_abaplint_json_reader~sub_section. + + DATA lo_section TYPE REF TO zcl_abaplint_json_reader. + DATA ls_item LIKE LINE OF mt_json_tree. + DATA lv_normalized_path TYPE string. + DATA ls_path_parts TYPE ty_path_name. + DATA lv_path_len TYPE i. + + CREATE OBJECT lo_section. + lv_normalized_path = normalize_path( iv_path ). + lv_path_len = strlen( lv_normalized_path ). + ls_path_parts = split_path( lv_normalized_path ). + + LOOP AT mt_json_tree INTO ls_item. + IF strlen( ls_item-path ) >= lv_path_len + AND substring( val = ls_item-path len = lv_path_len ) = lv_normalized_path. + ls_item-path = substring( val = ls_item-path off = lv_path_len - 1 ). " less closing '/' + INSERT ls_item INTO TABLE lo_section->mt_json_tree. + ELSEIF ls_item-path = ls_path_parts-path AND ls_item-name = ls_path_parts-name. + CLEAR: ls_item-path, ls_item-name. " this becomes a new root + INSERT ls_item INTO TABLE lo_section->mt_json_tree. + ENDIF. + ENDLOOP. + + ri_json = lo_section. + + ENDMETHOD. + + METHOD zif_abaplint_json_reader~value. DATA lv_item TYPE REF TO zif_abaplint_json_reader=>ty_node. diff --git a/src/zcl_abaplint_json_reader.clas.testclasses.abap b/src/zcl_abaplint_json_reader.clas.testclasses.abap index 77ebbcc7..c0b99834 100644 --- a/src/zcl_abaplint_json_reader.clas.testclasses.abap +++ b/src/zcl_abaplint_json_reader.clas.testclasses.abap @@ -17,6 +17,7 @@ CLASS ltcl_parser_test DEFINITION FINAL METHODS value_integer FOR TESTING RAISING zcx_abaplint_error. METHODS value_boolean FOR TESTING RAISING zcx_abaplint_error. METHODS members FOR TESTING RAISING zcx_abaplint_error. + METHODS sub_section FOR TESTING RAISING zcx_abaplint_error. DATA mt_exp TYPE zif_abaplint_json_reader=>ty_nodes_tt. METHODS _exp @@ -55,29 +56,62 @@ CLASS ltcl_parser_test IMPLEMENTATION. METHOD split_path. DATA ls_exp TYPE zcl_abaplint_json_reader=>ty_path_name. + DATA lv_path TYPE string. - ls_exp-path = '/'. + lv_path = ''. " alias to root + ls_exp-path = ''. ls_exp-name = ''. cl_abap_unit_assert=>assert_equals( - act = zcl_abaplint_json_reader=>split_path( '/' ) + act = zcl_abaplint_json_reader=>split_path( lv_path ) exp = ls_exp ). - ls_exp-path = '/abc/'. - ls_exp-name = 'xyz'. + lv_path = '/'. + ls_exp-path = ''. + ls_exp-name = ''. + cl_abap_unit_assert=>assert_equals( + act = zcl_abaplint_json_reader=>split_path( lv_path ) + exp = ls_exp ). + + lv_path = '/abc/'. + ls_exp-path = '/'. + ls_exp-name = 'abc'. + cl_abap_unit_assert=>assert_equals( + act = zcl_abaplint_json_reader=>split_path( lv_path ) + exp = ls_exp ). + + lv_path = 'abc'. + ls_exp-path = '/'. + ls_exp-name = 'abc'. + cl_abap_unit_assert=>assert_equals( + act = zcl_abaplint_json_reader=>split_path( lv_path ) + exp = ls_exp ). + + lv_path = '/abc'. + ls_exp-path = '/'. + ls_exp-name = 'abc'. + cl_abap_unit_assert=>assert_equals( + act = zcl_abaplint_json_reader=>split_path( lv_path ) + exp = ls_exp ). + + lv_path = 'abc/'. + ls_exp-path = '/'. + ls_exp-name = 'abc'. cl_abap_unit_assert=>assert_equals( - act = zcl_abaplint_json_reader=>split_path( '/abc/xyz' ) + act = zcl_abaplint_json_reader=>split_path( lv_path ) exp = ls_exp ). + lv_path = '/abc/xyz'. ls_exp-path = '/abc/'. - ls_exp-name = ''. + ls_exp-name = 'xyz'. cl_abap_unit_assert=>assert_equals( - act = zcl_abaplint_json_reader=>split_path( '/abc/' ) + act = zcl_abaplint_json_reader=>split_path( lv_path ) exp = ls_exp ). - ls_exp-path = '/abc/xyz/'. - ls_exp-name = ''. + lv_path = '/abc/xyz/'. + ls_exp-path = '/abc/'. + ls_exp-name = 'xyz'. cl_abap_unit_assert=>assert_equals( - act = zcl_abaplint_json_reader=>split_path( '/abc/xyz/' ) + act = zcl_abaplint_json_reader=>split_path( lv_path ) exp = ls_exp ). ENDMETHOD. @@ -190,6 +224,96 @@ CLASS ltcl_parser_test IMPLEMENTATION. ENDMETHOD. + METHOD sub_section. + + DATA lo_cut TYPE REF TO zcl_abaplint_json_reader. + + CLEAR mt_exp. + _exp( ' | |array | |2' ). + _exp( '/ |1 |object | |5' ). + _exp( '/1/ |message |str |Indentation problem ... |0' ). + _exp( '/1/ |key |str |indentation |0' ). + _exp( '/1/ |start |object | |2' ). + _exp( '/1/start/ |row |num |4 |0' ). + _exp( '/1/start/ |col |num |3 |0' ). + _exp( '/1/ |end |object | |2' ). + _exp( '/1/end/ |row |num |4 |0' ). + _exp( '/1/end/ |col |num |26 |0' ). + _exp( '/1/ |filename |str |./zxxx.prog.abap |0' ). + _exp( '/ |2 |object | |5' ). + _exp( '/2/ |message |str |Remove space before XXX |0' ). + _exp( '/2/ |key |str |space_before_dot |0' ). + _exp( '/2/ |start |object | |2' ). + _exp( '/2/start/ |row |num |3 |0' ). + _exp( '/2/start/ |col |num |21 |0' ). + _exp( '/2/ |end |object | |2' ). + _exp( '/2/end/ |row |num |3 |0' ). + _exp( '/2/end/ |col |num |22 |0' ). + _exp( '/2/ |filename |str |./zxxx.prog.abap |0' ). + SORT mt_exp BY path name. " Imitate sorted tab + + lo_cut = zcl_abaplint_json_reader=>parse( gv_sample ). + lo_cut ?= lo_cut->zif_abaplint_json_reader~sub_section( '/issues' ). + cl_abap_unit_assert=>assert_equals( + act = lo_cut->mt_json_tree + exp = mt_exp ). + + " ********************************************************************** + + CLEAR mt_exp. + _exp( ' | |object | |8' ). + _exp( '/ |string |str |abc |0' ). + _exp( '/ |number |num |123 |0' ). + _exp( '/ |float |num |123.45 |0' ). + _exp( '/ |boolean |bool |true |0' ). + _exp( '/ |false |bool |false |0' ). + _exp( '/ |null |null | |0' ). + _exp( '/ |date |str |2020-03-15 |0' ). + _exp( '/ |issues |array | |2' ). + _exp( '/issues/ |1 |object | |5' ). + _exp( '/issues/1/ |message |str |Indentation problem ... |0' ). + _exp( '/issues/1/ |key |str |indentation |0' ). + _exp( '/issues/1/ |start |object | |2' ). + _exp( '/issues/1/start/ |row |num |4 |0' ). + _exp( '/issues/1/start/ |col |num |3 |0' ). + _exp( '/issues/1/ |end |object | |2' ). + _exp( '/issues/1/end/ |row |num |4 |0' ). + _exp( '/issues/1/end/ |col |num |26 |0' ). + _exp( '/issues/1/ |filename |str |./zxxx.prog.abap |0' ). + _exp( '/issues/ |2 |object | |5' ). + _exp( '/issues/2/ |message |str |Remove space before XXX |0' ). + _exp( '/issues/2/ |key |str |space_before_dot |0' ). + _exp( '/issues/2/ |start |object | |2' ). + _exp( '/issues/2/start/ |row |num |3 |0' ). + _exp( '/issues/2/start/ |col |num |21 |0' ). + _exp( '/issues/2/ |end |object | |2' ). + _exp( '/issues/2/end/ |row |num |3 |0' ). + _exp( '/issues/2/end/ |col |num |22 |0' ). + _exp( '/issues/2/ |filename |str |./zxxx.prog.abap |0' ). + SORT mt_exp BY path name. " Imitate sorted tab + + lo_cut = zcl_abaplint_json_reader=>parse( gv_sample ). + lo_cut ?= lo_cut->zif_abaplint_json_reader~sub_section( '/' ). + cl_abap_unit_assert=>assert_equals( + act = lo_cut->mt_json_tree + exp = mt_exp ). + + " ********************************************************************** + + CLEAR mt_exp. + _exp( ' | |object | |2' ). + _exp( '/ |row |num |3 |0' ). + _exp( '/ |col |num |21 |0' ). + SORT mt_exp BY path name. " Imitate sorted tab + + lo_cut = zcl_abaplint_json_reader=>parse( gv_sample ). + lo_cut ?= lo_cut->zif_abaplint_json_reader~sub_section( '/issues/2/start/' ). + cl_abap_unit_assert=>assert_equals( + act = lo_cut->mt_json_tree + exp = mt_exp ). + + ENDMETHOD. + METHOD get_value. DATA lo_cut TYPE REF TO zif_abaplint_json_reader. @@ -201,7 +325,7 @@ CLASS ltcl_parser_test IMPLEMENTATION. cl_abap_unit_assert=>assert_equals( act = lo_cut->value( '/string/' ) - exp = '' ). + exp = 'abc' ). " Hmmm ? cl_abap_unit_assert=>assert_equals( act = lo_cut->value( '/boolean' ) @@ -225,7 +349,7 @@ CLASS ltcl_parser_test IMPLEMENTATION. cl_abap_unit_assert=>assert_equals( act = lo_cut->exists( '/string/' ) - exp = abap_false ). + exp = abap_true ). " mmmm ? cl_abap_unit_assert=>assert_equals( act = lo_cut->exists( '/xxx' ) diff --git a/src/zif_abaplint_json_reader.intf.abap b/src/zif_abaplint_json_reader.intf.abap index a088733b..b0bf2bce 100644 --- a/src/zif_abaplint_json_reader.intf.abap +++ b/src/zif_abaplint_json_reader.intf.abap @@ -48,5 +48,10 @@ INTERFACE zif_abaplint_json_reader !iv_path TYPE string RETURNING VALUE(rv_value) TYPE string . + METHODS sub_section + IMPORTING + !iv_path TYPE string + RETURNING + VALUE(ri_json) TYPE REF TO zif_abaplint_json_reader . ENDINTERFACE.