diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 6fb5cbc..69b4bad 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -3,7 +3,7 @@ name: Build & Test on: workflow_dispatch: pull_request: - branches: [ "main", "dev" ] + branches: [ "dev" ] schedule: - cron: "23 3 * * *" env: @@ -12,6 +12,9 @@ jobs: build: runs-on: ubuntu-latest environment: testing + concurrency: + group: testing + cancel-in-progress: true env: SSO_ID: ${{ vars.SSO_ID }} SSO_PASSWORD: ${{ secrets.SSO_PASSWORD }} @@ -21,6 +24,10 @@ jobs: - uses: actions/checkout@v4 - name: Setup Rust uses: dtolnay/rust-toolchain@stable + - name: Retrieve cache + uses: Leafwing-Studios/cargo-cache@v2 + with: + cache-group: build - name: Build run: cargo build --verbose - name: Run tests diff --git a/.github/workflows/dry-run.yml b/.github/workflows/dry-run.yml index 3697ace..6a0858e 100644 --- a/.github/workflows/dry-run.yml +++ b/.github/workflows/dry-run.yml @@ -6,9 +6,12 @@ on: env: CARGO_TERM_COLOR: always jobs: - build: + dry-run: runs-on: ubuntu-latest environment: testing + concurrency: + group: testing + cancel-in-progress: true env: SSO_ID: ${{ vars.SSO_ID }} SSO_PASSWORD: ${{ secrets.SSO_PASSWORD }} @@ -19,6 +22,8 @@ jobs: uses: actions/checkout@v4 - name: Setup Rust uses: dtolnay/rust-toolchain@stable + - name: Retrieve cache + uses: Leafwing-Studios/cargo-cache@v2 - name: Publish dry-run run: cargo publish -p rusaint --dry-run --verbose - name: Run tests diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..84e016f --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: Lint + +on: + workflow_dispatch: + pull_request: + branches: [ "main", "dev" ] +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Rust + uses: dtolnay/rust-toolchain@nightly + with: + components: clippy, rustfmt + - name: Retrieve cache + uses: Leafwing-Studios/cargo-cache@v2 + with: + cache-group: build + - name: Check rustfmt + run: cargo +nightly fmt + - name: Check clippy + run: cargo +nightly clippy -- -D warnings \ No newline at end of file diff --git a/.github/workflows/release-ios.yml b/.github/workflows/release-ios.yml new file mode 100644 index 0000000..5792db8 --- /dev/null +++ b/.github/workflows/release-ios.yml @@ -0,0 +1,150 @@ +name: Release (iOS) + +on: + push: + branches: [ dev ] + paths: + - './Cargo.toml' + workflow_dispatch: +permissions: + actions: write +jobs: + release-ios: + runs-on: macos-latest + steps: + - name: Checkout Cargo.toml to check version + uses: actions/checkout@v4 + with: + token: ${{ secrets.IOS_GITHUB_TOKEN }} + sparse-checkout: | + Cargo.toml + sparse-checkout-cone-mode: false + - name: Get rusaint version + id: current_version + uses: mikefarah/yq@v4 + with: + cmd: yq '.workspace.package.version | "v" + .' Cargo.toml + - name: Fetch latest release tag + id: latest_release + run: | + curl -L \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.IOS_GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/EATSTEAK/rusaint/releases \ + | jq '.[0].tag_name | "result=" + .' \ + | tr -d '"' >> $GITHUB_OUTPUT + - name: Cancel workflow if version is not updated + if: steps.current_version.outputs.result == steps.latest_release.outputs.result + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh run cancel ${{ github.run_id }} + gh run watch ${{ github.run_id }} + - name: Checkout the repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.IOS_GITHUB_TOKEN }} + submodules: 'recursive' + - name: Attach HEAD in submodule + working-directory: languages/swift/Rusaint + run: git switch main + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: 'aarch64-apple-ios, x86_64-apple-ios, aarch64-apple-ios-sim' + - name: Retrieve cache + uses: Leafwing-Studios/cargo-cache@v2 + - name: Install the Apple certificate + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.IOS_BUILD_CERTIFICATE_BASE64 }} + P12_PASSWORD: ${{ secrets.IOS_P12_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }} + run: | + # create variables + CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 + KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db + + # import certificate from secrets + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH + + # create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # import certificate to keychain + security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + - name: Build native library + env: + IPHONEOS_DEPLOYMENT_TARGET: ${{ vars.IOS_DEPLOYMENT_TARGET || '14.0' }} + RUSTFLAGS: '-C link-arg=-Wl,-application_extension' + run: | + mkdir $RUNNER_TEMP/target + CARGO_TARGET_DIR=$RUNNER_TEMP/target + cargo build --package rusaint-ffi --target aarch64-apple-ios-sim --release + cargo build --package rusaint-ffi --target aarch64-apple-ios --release + cargo build --package rusaint-ffi --target x86_64-apple-ios --release + - name: Create universal libraries for simulator + run: | + mkdir -p $RUNNER_TEMP/target/universal-ios-sim/release + lipo -create \ + $RUNNER_TEMP/target/aarch64-apple-ios-sim/release/librusaint_ffi.a \ + $RUNNER_TEMP/target/x86_64-apple-ios/release/librusaint_ffi.a \ + -output $RUNNER_TEMP/target/universal-ios-sim/release/librusaint_ffi.a + - name: Generate swift bindings + run: | + cargo run -p uniffi-bindgen generate \ + $RUNNER_TEMP/target/aarch64-apple-ios/release/librusaint_ffi.dylib \ + --library \ + --language swift \ + --no-format \ + --out-dir $RUNNER_TEMP/bindings + - name: Move generated swift bindgs + run: mv $RUNNER_TEMP/bindings/*.swift ./languages/swift/Rusaint/Sources/Rusaint/ + - name: Massage the generated files to fit xcframework + run: | + mkdir $RUNNER_TEMP/Headers + mv $RUNNER_TEMP/bindings/*.h $RUNNER_TEMP/Headers/ + cat $RUNNER_TEMP/bindings/*.modulemap > $RUNNER_TEMP/Headers/module.modulemap + - name: Create xcframework + run: | + rm -r ./languages/swift/Rusaint/Artifacts/RusaintFFI.xcframework + xcodebuild -create-xcframework \ + -library $RUNNER_TEMP/target/aarch64-apple-ios/release/librusaint_ffi.a \ + -headers $RUNNER_TEMP/Headers \ + -library $RUNNER_TEMP/target/universal-ios-sim/release/librusaint_ffi.a \ + -headers $RUNNER_TEMP/Headers \ + -output ./languages/swift/Rusaint/Artifacts/RusaintFFI.xcframework + - name: Sign xcframework + env: + KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }} + run: | + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $RUNNER_TEMP/signing.keychain-db + codesign --timestam-p -s "Apple Development" ./languages/swift/Rusaint/Artifacts/RusaintFFI.xcframework + - name: Push to submodule + working-directory: languages/swift/Rusaint + run: | + git config user.name "GitHub Actions" + git config user.email "" + git add . + git commit -m "release: ${{ steps.current_version.outputs.result }}" + git push + - name: Commit submodule in main repository + run: | + git config user.name "GitHub Actions" + git config user.email "" + git add languages/swift/Rusaint + git commit -m "release(ios): ${{ steps.current_version.outputs.result }}" + git push + - name: Create release + run: | + curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.IOS_GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/EATSTEAK/rusaint-ios/releases \ + -d '{"tag_name":"${{ steps.current_version.outputs.result }}","name":"${{ steps.current_version.outputs.result }}"}' \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9e719fb..975bcbc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,16 +8,47 @@ on: env: CARGO_TERM_COLOR: always jobs: - build: + release: runs-on: ubuntu-latest - environment: testing steps: + - name: Checkout Cargo.toml to check version + uses: actions/checkout@v4 + with: + token: ${{ secrets.IOS_GITHUB_TOKEN }} + sparse-checkout: | + Cargo.toml + sparse-checkout-cone-mode: false + - name: Get rusaint version + id: current_version + uses: mikefarah/yq@v4 + with: + cmd: yq '.workspace.package.version | "v" + .' Cargo.toml + - name: Fetch latest release tag + id: latest_release + run: | + curl -L \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.IOS_GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/EATSTEAK/rusaint/releases \ + | jq '.[0].tag_name | "result=" + .' \ + | tr -d '"' >> $GITHUB_OUTPUT + - name: Cancel workflow if version is not updated + if: steps.current_version.outputs.result == steps.latest_release.outputs.result + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh run cancel ${{ github.run_id }} + gh run watch ${{ github.run_id }} - name: Checkout uses: actions/checkout@v4 - name: Setup Rust uses: dtolnay/rust-toolchain@stable with: targets: 'armv7-linux-androideabi, i686-linux-android, aarch64-linux-android, x86_64-linux-android' + - name: Retrieve cache + uses: Leafwing-Studios/cargo-cache@v2 + with: - name: Setup Java uses: actions/setup-java@v4 with: @@ -27,7 +58,10 @@ jobs: - name: Setup Android SDK uses: android-actions/setup-android@v3 - name: Setup Android NDK - run: 'sdkmanager "ndk;27.2.12479018"' + uses: nttld/setup-ndk@v1 + with: + ndk-version: r27c + link-to-sdk: true - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - name: Build with Gradle diff --git a/languages/swift/Rusaint b/languages/swift/Rusaint index e49d388..42d58e1 160000 --- a/languages/swift/Rusaint +++ b/languages/swift/Rusaint @@ -1 +1 @@ -Subproject commit e49d38824d97f5775d49375cfb3e3498b716ed21 +Subproject commit 42d58e1de286cb2f5ef9f9a18e79f64198323e2c diff --git a/languages/swift/build.sh b/languages/swift/build.sh index e218027..90ed0a5 100755 --- a/languages/swift/build.sh +++ b/languages/swift/build.sh @@ -21,7 +21,7 @@ lipo -create ../../target/aarch64-apple-ios-sim/release/librusaint_ffi.a \ # Generate swift bindings cargo run -p uniffi-bindgen generate \ - ../../target/aarch64-apple-ios-sim/release/librusaint_ffi.dylib \ + ../../target/aarch64-apple-ios/release/librusaint_ffi.dylib \ --library \ --language swift \ --no-format \ diff --git a/packages/rusaint/src/application/course_grades/mod.rs b/packages/rusaint/src/application/course_grades/mod.rs index 8604a0d..4b21c99 100644 --- a/packages/rusaint/src/application/course_grades/mod.rs +++ b/packages/rusaint/src/application/course_grades/mod.rs @@ -1,5 +1,6 @@ use self::model::{ClassGrade, CourseType, GradeSummary, SemesterGrade}; use super::{USaintApplication, USaintClient}; +use crate::application::utils::sap_table::try_table_into_with_scroll; use crate::webdynpro::client::body::Body; use crate::webdynpro::command::WebDynproCommandExecutor; use crate::webdynpro::element::complex::sap_table::cell::SapTableCellWrapper; @@ -295,13 +296,17 @@ impl<'a> CourseGradesApplication { self.close_popups().await?; let parser = ElementParser::new(self.client.body()); self.select_course(&parser, course_type).await?; - self.read_semesters() + self.read_semesters().await } - fn read_semesters(&self) -> Result, RusaintError> { + async fn read_semesters(&mut self) -> Result, RusaintError> { let parser = ElementParser::new(self.client.body()); - let table = parser.read(SapTableBodyCommand::new(Self::GRADES_SUMMARY_TABLE))?; - let ret = table.try_table_into::(&parser)?; + let ret = try_table_into_with_scroll::( + &mut self.client, + parser, + Self::GRADES_SUMMARY_TABLE, + ) + .await?; Ok(ret) } diff --git a/packages/rusaint/src/application/course_schedule/mod.rs b/packages/rusaint/src/application/course_schedule/mod.rs index 6487753..d8b9ce4 100644 --- a/packages/rusaint/src/application/course_schedule/mod.rs +++ b/packages/rusaint/src/application/course_schedule/mod.rs @@ -1,4 +1,5 @@ use super::{USaintApplication, USaintClient}; +use crate::application::utils::sap_table::try_table_into_with_scroll; use crate::webdynpro::command::WebDynproCommandExecutor; use crate::webdynpro::element::parser::ElementParser; use crate::{ @@ -128,7 +129,9 @@ impl<'a> CourseScheduleApplication { } } } - let lectures = table.try_table_into::(&parser)?; + let lectures = + try_table_into_with_scroll::(&mut self.client, parser, Self::MAIN_TABLE) + .await?; Ok(lectures.into_iter()) } diff --git a/packages/rusaint/src/application/lecture_assessment/mod.rs b/packages/rusaint/src/application/lecture_assessment/mod.rs index 81a1c16..c68e8ab 100644 --- a/packages/rusaint/src/application/lecture_assessment/mod.rs +++ b/packages/rusaint/src/application/lecture_assessment/mod.rs @@ -1,6 +1,7 @@ use model::LectureAssessmentResult; use super::{USaintApplication, USaintClient}; +use crate::application::utils::sap_table::try_table_into_with_scroll; use crate::webdynpro::command::WebDynproCommandExecutor; use crate::webdynpro::element::parser::ElementParser; use crate::{ @@ -10,9 +11,7 @@ use crate::{ client::body::Body, command::element::{ action::ButtonPressEventCommand, - complex::{ - SapTableBodyCommand, SapTableLSDataCommand, SapTableVerticalScrollEventCommand, - }, + complex::{SapTableBodyCommand, SapTableLSDataCommand}, selection::{ ComboBoxChangeEventCommand, ComboBoxLSDataCommand, ComboBoxSelectEventCommand, }, @@ -143,7 +142,7 @@ impl<'a> LectureAssessmentApplication { professor_name, ) .await?; - let mut parser = ElementParser::new(self.body()); + let parser = ElementParser::new(self.body()); let row_count = parser .read(SapTableLSDataCommand::new(Self::TABLE))? .row_count() @@ -153,10 +152,8 @@ impl<'a> LectureAssessmentApplication { element: Self::TABLE.id().to_string(), field: "row_count".to_string(), }) - })? - .try_into() - .unwrap(); - let mut table = parser.read(SapTableBodyCommand::new(Self::TABLE))?; + })?; + let table = parser.read(SapTableBodyCommand::new(Self::TABLE))?; if row_count == 1 { let Some(first_row) = table.iter().next() else { return Err(ApplicationError::NoLectureAssessments.into()); @@ -173,31 +170,7 @@ impl<'a> LectureAssessmentApplication { } } } - let mut results: Vec = Vec::with_capacity(row_count); - while results.len() < row_count { - let mut partial_results = table.try_table_into::(&parser)?; - if results.len() + partial_results.len() > row_count { - let overflowed = results.len() + partial_results.len() - row_count; - partial_results.drain(0..overflowed); - } - results.append(&mut partial_results); - if results.len() < row_count { - let event = parser.read(SapTableVerticalScrollEventCommand::new( - Self::TABLE, - results.len().try_into().unwrap(), - "", - "SCROLLBAR", - false, - false, - false, - false, - ))?; - self.client.process_event(false, event).await?; - parser = ElementParser::new(self.body()); - table = parser.read(SapTableBodyCommand::new(Self::TABLE))?; - } - } - Ok(results) + Ok(try_table_into_with_scroll(&mut self.client, parser, Self::TABLE).await?) } } diff --git a/packages/rusaint/src/application/student_information/model/academic_record.rs b/packages/rusaint/src/application/student_information/model/academic_record.rs index 3fcdb2f..3262be0 100644 --- a/packages/rusaint/src/application/student_information/model/academic_record.rs +++ b/packages/rusaint/src/application/student_information/model/academic_record.rs @@ -5,13 +5,14 @@ use serde::{ Deserialize, }; +use crate::application::utils::sap_table::try_table_into_with_scroll; use crate::webdynpro::command::WebDynproCommandExecutor; use crate::webdynpro::element::parser::ElementParser; use crate::{ application::{student_information::StudentInformationApplication, USaintClient}, define_elements, webdynpro::{ - command::element::{complex::SapTableBodyCommand, layout::TabStripTabSelectEventCommand}, + command::element::layout::TabStripTabSelectEventCommand, element::{ complex::{sap_table::FromSapTable, SapTable}, definition::ElementDefinition, @@ -47,8 +48,9 @@ impl<'a> StudentAcademicRecords { ))?; client.process_event(false, event).await?; parser = ElementParser::new(client.body()); - let table = parser.read(SapTableBodyCommand::new(Self::TABLE_9600))?; - let records = table.try_table_into::(&parser)?; + let records = + try_table_into_with_scroll::(client, parser, Self::TABLE_9600) + .await?; Ok(Self { records }) } diff --git a/packages/rusaint/src/application/student_information/model/family.rs b/packages/rusaint/src/application/student_information/model/family.rs index 65b24b8..436d3ac 100644 --- a/packages/rusaint/src/application/student_information/model/family.rs +++ b/packages/rusaint/src/application/student_information/model/family.rs @@ -6,13 +6,14 @@ use serde::{ }; use crate::application::utils::de_with::{deserialize_bool_string, deserialize_optional_string}; +use crate::application::utils::sap_table::try_table_into_with_scroll; use crate::webdynpro::command::WebDynproCommandExecutor; use crate::webdynpro::element::parser::ElementParser; use crate::{ application::{student_information::StudentInformationApplication, USaintClient}, define_elements, webdynpro::{ - command::element::{complex::SapTableBodyCommand, layout::TabStripTabSelectEventCommand}, + command::element::layout::TabStripTabSelectEventCommand, element::{ complex::{sap_table::FromSapTable, SapTable}, definition::ElementDefinition, @@ -48,8 +49,9 @@ impl<'a> StudentFamily { ))?; client.process_event(false, event).await?; parser = ElementParser::new(client.body()); - let table = parser.read(SapTableBodyCommand::new(Self::TABLE_FAMILY))?; - let members = table.try_table_into::(&parser)?; + let members = + try_table_into_with_scroll::(client, parser, Self::TABLE_FAMILY) + .await?; Ok(Self { members }) } diff --git a/packages/rusaint/src/application/student_information/model/transfer.rs b/packages/rusaint/src/application/student_information/model/transfer.rs index f8d952b..564abcf 100644 --- a/packages/rusaint/src/application/student_information/model/transfer.rs +++ b/packages/rusaint/src/application/student_information/model/transfer.rs @@ -5,13 +5,14 @@ use serde::{ Deserialize, }; +use crate::application::utils::sap_table::try_table_into_with_scroll; use crate::webdynpro::command::WebDynproCommandExecutor; use crate::webdynpro::element::parser::ElementParser; use crate::{ application::{student_information::StudentInformationApplication, USaintClient}, define_elements, webdynpro::{ - command::element::{complex::SapTableBodyCommand, layout::TabStripTabSelectEventCommand}, + command::element::layout::TabStripTabSelectEventCommand, element::{ complex::{sap_table::FromSapTable, SapTable}, definition::ElementDefinition, @@ -47,8 +48,12 @@ impl<'a> StudentTransferRecords { ))?; client.process_event(false, event).await?; parser = ElementParser::new(client.body()); - let table = parser.read(SapTableBodyCommand::new(Self::TABLE_TRANSFER))?; - let records = table.try_table_into::(&parser)?; + let records = try_table_into_with_scroll::( + client, + parser, + Self::TABLE_TRANSFER, + ) + .await?; Ok(Self { records }) } diff --git a/packages/rusaint/src/application/utils/mod.rs b/packages/rusaint/src/application/utils/mod.rs index db02525..eb7eab6 100644 --- a/packages/rusaint/src/application/utils/mod.rs +++ b/packages/rusaint/src/application/utils/mod.rs @@ -1,2 +1,3 @@ pub(crate) mod de_with; pub(crate) mod input_field; +pub(crate) mod sap_table; diff --git a/packages/rusaint/src/application/utils/sap_table.rs b/packages/rusaint/src/application/utils/sap_table.rs new file mode 100644 index 0000000..151e8aa --- /dev/null +++ b/packages/rusaint/src/application/utils/sap_table.rs @@ -0,0 +1,55 @@ +use crate::application::USaintClient; +use crate::webdynpro::command::element::complex::{ + SapTableBodyCommand, SapTableLSDataCommand, SapTableVerticalScrollEventCommand, +}; +use crate::webdynpro::command::WebDynproCommandExecutor; +use crate::webdynpro::element::complex::sap_table::FromSapTable; +use crate::webdynpro::element::complex::SapTableDef; +use crate::webdynpro::element::definition::ElementDefinition; +use crate::webdynpro::element::parser::ElementParser; +use crate::webdynpro::error::{ElementError, WebDynproError}; + +pub(crate) async fn try_table_into_with_scroll FromSapTable<'body>>( + client: &mut USaintClient, + mut parser: ElementParser, + table: SapTableDef, +) -> Result, WebDynproError> { + let row_count = parser + .read(SapTableLSDataCommand::new(table.clone()))? + .row_count() + .map(|u| u.to_owned()) + .ok_or_else(|| { + WebDynproError::Element(ElementError::NoSuchData { + element: table.clone().id().to_string(), + field: "row_count".to_string(), + }) + })? + .try_into() + .unwrap(); + let mut table_body = parser.read(SapTableBodyCommand::new(table.clone()))?; + let mut results: Vec = Vec::with_capacity(row_count); + while results.len() < row_count { + let mut partial_results = table_body.try_table_into::(&parser)?; + if results.len() + partial_results.len() > row_count { + let overflowed = results.len() + partial_results.len() - row_count; + partial_results.drain(0..overflowed); + } + results.append(&mut partial_results); + if results.len() < row_count { + let event = parser.read(SapTableVerticalScrollEventCommand::new( + table.clone(), + results.len().try_into().unwrap(), + "", + "SCROLLBAR", + false, + false, + false, + false, + ))?; + client.process_event(false, event).await?; + parser = ElementParser::new(client.body()); + table_body = parser.read(SapTableBodyCommand::new(table.clone()))?; + } + } + Ok(results) +} diff --git a/packages/rusaint/src/webdynpro/client/mod.rs b/packages/rusaint/src/webdynpro/client/mod.rs index d5b2ca8..d517d51 100644 --- a/packages/rusaint/src/webdynpro/client/mod.rs +++ b/packages/rusaint/src/webdynpro/client/mod.rs @@ -38,7 +38,7 @@ fn wd_xhr_header() -> HeaderMap { headers } -impl<'a> WebDynproClient { +impl WebDynproClient { /// WebDynpro 애플리케이션의 이름을 반환합니다. pub fn name(&self) -> &str { &self.name